Skip to content

Commit e29ec88

Browse files
committed
fix: restore backups + fork lineage (#1) (thanks @joshp123)
1 parent 0eb3047 commit e29ec88

File tree

7 files changed

+42
-20
lines changed

7 files changed

+42
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
- Web: stabilize skill OG image generation on server runtimes.
1313
- Web: prevent skill OG text overflow outside the card.
1414
- Registry: make SoulHub auto-seed idempotent and non-user-owned.
15+
- Registry: keep GitHub backup state + publish backups intact (thanks @joshp123, #1).
16+
- CLI/Registry: restore fork lineage on sync + clamp bulk list queries (thanks @joshp123, #1).
1517

1618
## 0.0.6 - 2026-01-07
1719

convex/schema.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,12 @@ const rateLimits = defineTable({
268268
.index('by_key_window', ['key', 'windowStart'])
269269
.index('by_key', ['key'])
270270

271+
const githubBackupSyncState = defineTable({
272+
key: v.string(),
273+
cursor: v.optional(v.string()),
274+
updatedAt: v.number(),
275+
}).index('by_key', ['key'])
276+
271277
const userSyncRoots = defineTable({
272278
userId: v.id('users'),
273279
rootId: v.string(),
@@ -324,6 +330,7 @@ export default defineSchema({
324330
auditLogs,
325331
apiTokens,
326332
rateLimits,
333+
githubBackupSyncState,
327334
userSyncRoots,
328335
userSkillInstalls,
329336
userSkillRootInstalls,

convex/skills.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type FileTextResult = { path: string; text: string; size: number; sha256: string
2020

2121
const MAX_DIFF_FILE_BYTES = 200 * 1024
2222
const MAX_LIST_LIMIT = 50
23+
const MAX_LIST_BULK_LIMIT = 200
24+
const MAX_LIST_TAKE = 1000
2325

2426
export const getBySlug = query({
2527
args: { slug: v.string() },
@@ -87,13 +89,14 @@ export const list = query({
8789
limit: v.optional(v.number()),
8890
},
8991
handler: async (ctx, args) => {
90-
const limit = args.limit ?? 24
92+
const limit = clampInt(args.limit ?? 24, 1, MAX_LIST_BULK_LIMIT)
93+
const takeLimit = Math.min(limit * 5, MAX_LIST_TAKE)
9194
if (args.batch) {
9295
const entries = await ctx.db
9396
.query('skills')
9497
.withIndex('by_batch', (q) => q.eq('batch', args.batch))
9598
.order('desc')
96-
.take(limit * 5)
99+
.take(takeLimit)
97100
return entries.filter((skill) => !skill.softDeletedAt).slice(0, limit)
98101
}
99102
const ownerUserId = args.ownerUserId
@@ -102,13 +105,10 @@ export const list = query({
102105
.query('skills')
103106
.withIndex('by_owner', (q) => q.eq('ownerUserId', ownerUserId))
104107
.order('desc')
105-
.take(limit * 5)
108+
.take(takeLimit)
106109
return entries.filter((skill) => !skill.softDeletedAt).slice(0, limit)
107110
}
108-
const entries = await ctx.db
109-
.query('skills')
110-
.order('desc')
111-
.take(limit * 5)
111+
const entries = await ctx.db.query('skills').order('desc').take(takeLimit)
112112
return entries.filter((skill) => !skill.softDeletedAt).slice(0, limit)
113113
},
114114
})
@@ -120,25 +120,23 @@ export const listWithLatest = query({
120120
limit: v.optional(v.number()),
121121
},
122122
handler: async (ctx, args) => {
123-
const limit = args.limit ?? 24
123+
const limit = clampInt(args.limit ?? 24, 1, MAX_LIST_BULK_LIMIT)
124+
const takeLimit = Math.min(limit * 5, MAX_LIST_TAKE)
124125
let entries: Doc<'skills'>[] = []
125126
if (args.batch) {
126127
entries = await ctx.db
127128
.query('skills')
128129
.withIndex('by_batch', (q) => q.eq('batch', args.batch))
129130
.order('desc')
130-
.take(limit * 5)
131+
.take(takeLimit)
131132
} else if (args.ownerUserId) {
132133
entries = await ctx.db
133134
.query('skills')
134135
.withIndex('by_owner', (q) => q.eq('ownerUserId', args.ownerUserId))
135136
.order('desc')
136-
.take(limit * 5)
137+
.take(takeLimit)
137138
} else {
138-
entries = await ctx.db
139-
.query('skills')
140-
.order('desc')
141-
.take(limit * 5)
139+
entries = await ctx.db.query('skills').order('desc').take(takeLimit)
142140
}
143141

144142
const filtered = entries.filter((skill) => !skill.softDeletedAt).slice(0, limit)

packages/clawdhub/src/cli/commands/sync.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { intro, outro } from '@clack/prompts'
22
import { readGlobalConfig } from '../../config.js'
3-
import { hashSkillFiles, listTextFiles } from '../../skills.js'
3+
import { hashSkillFiles, listTextFiles, readSkillOrigin } from '../../skills.js'
44
import { resolveClawdbotSkillRoots } from '../clawdbotConfig.js'
55
import { getFallbackSkillRoots } from '../scanSkills.js'
66
import type { GlobalOpts } from '../types.js'
@@ -84,12 +84,14 @@ export async function cmdSync(opts: GlobalOpts, options: SyncOptions, inputAllow
8484
const parsed = await mapWithConcurrency(skills, Math.min(concurrency, 12), async (skill) => {
8585
const filesOnDisk = await listTextFiles(skill.folder)
8686
const hashed = hashSkillFiles(filesOnDisk)
87+
const origin = await readSkillOrigin(skill.folder)
8788
done += 1
8889
parsingSpinner.text = `Parsing local skills ${done}/${skills.length}`
8990
return {
9091
...skill,
9192
fingerprint: hashed.fingerprint,
9293
fileCount: filesOnDisk.length,
94+
origin,
9395
}
9496
})
9597
locals.push(...parsed)
@@ -174,14 +176,25 @@ export async function cmdSync(opts: GlobalOpts, options: SyncOptions, inputAllow
174176
allowPrompt,
175177
changelogFlag: options.changelog,
176178
})
179+
const forkOf =
180+
skill.origin && normalizeRegistry(skill.origin.registry) === normalizeRegistry(registry)
181+
? skill.origin.slug !== skill.slug
182+
? `${skill.origin.slug}@${skill.origin.installedVersion}`
183+
: undefined
184+
: undefined
177185
await cmdPublish(opts, skill.folder, {
178186
slug: skill.slug,
179187
name: skill.displayName,
180188
version: publishVersion,
181189
changelog,
182190
tags,
191+
forkOf,
183192
})
184193
}
185194

186195
outro(`Uploaded ${selected.length} skill(s).`)
187196
}
197+
198+
function normalizeRegistry(value: string) {
199+
return value.trim().replace(/\/+$/, '').toLowerCase()
200+
}

packages/clawdhub/src/cli/commands/syncTypes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { SkillOrigin } from '../../skills.js'
12
import type { SkillFolder } from '../scanSkills.js'
23

34
export type SyncOptions = {
@@ -13,6 +14,7 @@ export type SyncOptions = {
1314
export type Candidate = SkillFolder & {
1415
fingerprint: string
1516
fileCount: number
17+
origin: SkillOrigin | null
1618
status: 'synced' | 'new' | 'update'
1719
matchVersion: string | null
1820
latestVersion: string | null
@@ -21,4 +23,5 @@ export type Candidate = SkillFolder & {
2123
export type LocalSkill = SkillFolder & {
2224
fingerprint: string
2325
fileCount: number
26+
origin: SkillOrigin | null
2427
}

src/routeTree.gen.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,10 @@ export const routeTree = rootRouteImport
357357
._addFileTypes<FileRouteTypes>()
358358

359359
import type { getRouter } from './router.tsx'
360-
import type { startInstance } from './start.ts'
360+
import type { createStart } from '@tanstack/react-start'
361361
declare module '@tanstack/react-start' {
362362
interface Register {
363363
ssr: true
364364
router: Awaited<ReturnType<typeof getRouter>>
365-
config: Awaited<ReturnType<typeof startInstance.getOptions>>
366365
}
367366
}

src/routes/skills/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ function SkillsIndex() {
8383
return (a.skill.updatedAt - b.skill.updatedAt) * multiplier
8484
case 'name':
8585
return (
86-
a.skill.displayName.localeCompare(b.skill.displayName) ||
87-
a.skill.slug.localeCompare(b.skill.slug)
88-
) * multiplier
86+
(a.skill.displayName.localeCompare(b.skill.displayName) ||
87+
a.skill.slug.localeCompare(b.skill.slug)) * multiplier
88+
)
8989
default:
9090
return (a.skill.createdAt - b.skill.createdAt) * multiplier
9191
}

0 commit comments

Comments
 (0)