0101-agents 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +284 -80
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// 0101-agents CLI — installs and updates
|
|
2
|
+
// 0101-agents CLI — installs and updates 0101 skillsets and agent templates.
|
|
3
3
|
//
|
|
4
4
|
// Commands:
|
|
5
5
|
// 0101-agents login [<license-key>]
|
|
6
6
|
// 0101-agents logout
|
|
7
7
|
// 0101-agents whoami
|
|
8
|
-
// 0101-agents install <agent>
|
|
9
|
-
// 0101-agents
|
|
8
|
+
// 0101-agents install <set> --to <agent> install a skillset into an agent
|
|
9
|
+
// 0101-agents install base --as <name> create a new agent from the base template
|
|
10
|
+
// 0101-agents update <set> --to <agent> update a skillset inside an agent
|
|
11
|
+
// 0101-agents update <agent> [-f] update an agent's template (preserves data/)
|
|
10
12
|
// 0101-agents list
|
|
11
13
|
// 0101-agents help
|
|
12
14
|
|
|
@@ -81,9 +83,14 @@ async function fetchManifest(agent) {
|
|
|
81
83
|
if (res.status === 401) die('License invalid. Run: 0101-agents login')
|
|
82
84
|
if (res.status === 403) {
|
|
83
85
|
const body = await res.json().catch(() => ({}))
|
|
84
|
-
die(body.error || 'License does not cover this
|
|
86
|
+
die((body.error || 'License does not cover this artifact.') +
|
|
87
|
+
'\n Skillsets must be added to your account first: https://0101.fyi/catalog')
|
|
88
|
+
}
|
|
89
|
+
if (res.status === 404) {
|
|
90
|
+
die(`Not found: ${agent}\n` +
|
|
91
|
+
' Available skillsets: https://0101.fyi/catalog\n' +
|
|
92
|
+
' New agent: 0101-agents install base --as <name>')
|
|
85
93
|
}
|
|
86
|
-
if (res.status === 404) die(`Agent not found: ${agent}`)
|
|
87
94
|
if (!res.ok) die(`Manifest fetch failed: ${res.status} ${res.statusText}`)
|
|
88
95
|
return res.json()
|
|
89
96
|
}
|
|
@@ -199,6 +206,138 @@ async function prompt(question) {
|
|
|
199
206
|
try { return (await rl.question(question)).trim() } finally { rl.close() }
|
|
200
207
|
}
|
|
201
208
|
|
|
209
|
+
// ─── sets ────────────────────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
function getFlag(args, name) {
|
|
212
|
+
const i = args.indexOf(name)
|
|
213
|
+
return i !== -1 && args[i + 1] && !args[i + 1].startsWith('-') ? args[i + 1] : null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Per-agent record of installed sets: ~/0101/<agent>/.0101-sets.json
|
|
217
|
+
// { "<set-slug>": { "version": "0.0.2", "skills": ["ad-creative", ...] } }
|
|
218
|
+
// (Older entries may be plain version strings — normalized on read.)
|
|
219
|
+
function readSetsFile(agent) {
|
|
220
|
+
try {
|
|
221
|
+
const raw = JSON.parse(readFileSync(join(agentInstallDir(agent), '.0101-sets.json'), 'utf8'))
|
|
222
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
223
|
+
if (typeof v === 'string') raw[k] = { version: v, skills: [] }
|
|
224
|
+
}
|
|
225
|
+
return raw
|
|
226
|
+
} catch {
|
|
227
|
+
return {}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function setVersion(sets, slug) {
|
|
232
|
+
return sets[slug]?.version ?? null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function writeSetsFile(agent, sets) {
|
|
236
|
+
writeFileSync(
|
|
237
|
+
join(agentInstallDir(agent), '.0101-sets.json'),
|
|
238
|
+
JSON.stringify(sets, null, 2) + '\n',
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Fetch a set artifact and install its skills into the agent's .claude/skills/.
|
|
243
|
+
//
|
|
244
|
+
// Safety model:
|
|
245
|
+
// - Each skillset OWNS the skill folders it shipped (recorded per-set in
|
|
246
|
+
// .0101-sets.json). On install/update those folders are clean-replaced
|
|
247
|
+
// (delete + copy), so files removed upstream don't linger.
|
|
248
|
+
// - Skill folders the set DOESN'T ship are never touched — the agent's own
|
|
249
|
+
// skills and other skillsets are safe.
|
|
250
|
+
// - Name collision (a folder exists that this set didn't previously own,
|
|
251
|
+
// e.g. a hand-made skill with the same name): backed up to
|
|
252
|
+
// ~/0101/.backups/ first, then replaced, with a warning.
|
|
253
|
+
// - A skill the set used to ship but no longer does is removed (it was ours).
|
|
254
|
+
async function installSetInto(setSlug, agent) {
|
|
255
|
+
const agentDir = agentInstallDir(agent)
|
|
256
|
+
if (!existsSync(agentDir)) {
|
|
257
|
+
die(`${agent} is not installed at ${agentDir}.\n` +
|
|
258
|
+
` Create it first: 0101-agents install base --as ${agent}`)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const manifest = await fetchManifest(setSlug)
|
|
262
|
+
const zipPath = join(tmpdir(), `0101-set-${setSlug}-${manifest.latest}.zip`)
|
|
263
|
+
await downloadZip(manifest.download_url, zipPath)
|
|
264
|
+
|
|
265
|
+
const tmpDir = join(tmpdir(), `0101-set-${setSlug}-${Date.now()}`)
|
|
266
|
+
unzipInto(zipPath, tmpDir)
|
|
267
|
+
rmSync(zipPath, { force: true })
|
|
268
|
+
|
|
269
|
+
const skillsSrc = join(tmpDir, '.claude', 'skills')
|
|
270
|
+
if (!existsSync(skillsSrc)) {
|
|
271
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
272
|
+
die(`${setSlug} is not a skillset (no .claude/skills/ in the artifact).`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const newSkills = readdirSync(skillsSrc)
|
|
276
|
+
.filter(d => statSync(join(skillsSrc, d)).isDirectory())
|
|
277
|
+
const skillsDest = join(agentDir, '.claude', 'skills')
|
|
278
|
+
mkdirSync(skillsDest, { recursive: true })
|
|
279
|
+
|
|
280
|
+
const sets = readSetsFile(agent)
|
|
281
|
+
const prevSkills = sets[setSlug]?.skills ?? []
|
|
282
|
+
|
|
283
|
+
// Collisions: folders that exist but weren't shipped by this set before.
|
|
284
|
+
const foreign = newSkills.filter(
|
|
285
|
+
s => existsSync(join(skillsDest, s)) && !prevSkills.includes(s),
|
|
286
|
+
)
|
|
287
|
+
// Skills this set used to ship but no longer does (removed upstream).
|
|
288
|
+
const dropped = prevSkills.filter(
|
|
289
|
+
s => !newSkills.includes(s) && existsSync(join(skillsDest, s)),
|
|
290
|
+
)
|
|
291
|
+
// Back up anything we're about to replace-with-different or delete, so a
|
|
292
|
+
// hand-made collision or an upstream removal is always recoverable.
|
|
293
|
+
const toBackup = [...foreign, ...dropped]
|
|
294
|
+
if (toBackup.length) {
|
|
295
|
+
mkdirSync(BACKUPS_DIR, { recursive: true })
|
|
296
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-')
|
|
297
|
+
const backupPath = join(BACKUPS_DIR, `${agent}-skills-${ts}.tar.gz`)
|
|
298
|
+
execSync(
|
|
299
|
+
`tar -czf "${backupPath}" -C "${skillsDest}" ${toBackup.map(s => `"${s}"`).join(' ')}`,
|
|
300
|
+
{ stdio: 'inherit' },
|
|
301
|
+
)
|
|
302
|
+
if (foreign.length) {
|
|
303
|
+
console.log(dim(` ⚠ Replacing existing skill(s) ${foreign.join(', ')} ` +
|
|
304
|
+
`(backed up → ${backupPath})`))
|
|
305
|
+
}
|
|
306
|
+
if (dropped.length) {
|
|
307
|
+
console.log(dim(` Backup of removed skill(s) → ${backupPath}`))
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Clean-replace every skill this set ships.
|
|
312
|
+
for (const s of newSkills) {
|
|
313
|
+
rmSync(join(skillsDest, s), { recursive: true, force: true })
|
|
314
|
+
execSync(`cp -R "${join(skillsSrc, s)}" "${join(skillsDest, s)}"`)
|
|
315
|
+
}
|
|
316
|
+
// Remove skills this set used to ship but no longer does.
|
|
317
|
+
for (const s of prevSkills.filter(s => !newSkills.includes(s))) {
|
|
318
|
+
rmSync(join(skillsDest, s), { recursive: true, force: true })
|
|
319
|
+
console.log(dim(` − removed ${s} (no longer part of ${setSlug})`))
|
|
320
|
+
}
|
|
321
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
322
|
+
|
|
323
|
+
sets[setSlug] = { version: manifest.latest, skills: newSkills }
|
|
324
|
+
writeSetsFile(agent, sets)
|
|
325
|
+
return manifest.latest
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// The 0101 agent manages itself (its own update skill) and is downloaded from
|
|
329
|
+
// the website — it is deliberately NOT installable or updatable via this CLI,
|
|
330
|
+
// so there's exactly one update mechanism per artifact.
|
|
331
|
+
const SELF_MANAGED = new Set(['0101'])
|
|
332
|
+
|
|
333
|
+
function rejectSelfManaged(agent, action) {
|
|
334
|
+
if (SELF_MANAGED.has(agent)) {
|
|
335
|
+
die(`The 0101 agent manages itself — it can't be ${action} via this CLI.\n` +
|
|
336
|
+
` Download it at https://0101.fyi and open the folder in Claude Code;\n` +
|
|
337
|
+
` it updates itself from inside.`)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
202
341
|
// ─── commands ────────────────────────────────────────────────────────────────
|
|
203
342
|
|
|
204
343
|
async function cmdLogin(args) {
|
|
@@ -230,21 +369,44 @@ async function cmdWhoami() {
|
|
|
230
369
|
}
|
|
231
370
|
|
|
232
371
|
async function cmdInstall(args) {
|
|
233
|
-
const
|
|
234
|
-
if (!
|
|
372
|
+
const slug = args[0] && !args[0].startsWith('-') ? args[0] : null
|
|
373
|
+
if (!slug) {
|
|
374
|
+
die('Usage: 0101-agents install <set> --to <agent>\n' +
|
|
375
|
+
' 0101-agents install base --as <name>')
|
|
376
|
+
}
|
|
377
|
+
rejectSelfManaged(slug, 'installed')
|
|
378
|
+
ensureUnzipAvailable()
|
|
379
|
+
|
|
380
|
+
// ── set into an existing agent ──
|
|
381
|
+
const to = getFlag(args, '--to')
|
|
382
|
+
if (to) {
|
|
383
|
+
info(`Installing set ${slug} into ${to}…`)
|
|
384
|
+
const v = await installSetInto(slug, to)
|
|
385
|
+
console.log('')
|
|
386
|
+
ok(`Set ${bold(slug)} ${v} → ${to}`)
|
|
387
|
+
info('New skills load at the agent’s next session start.')
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ── whole artifact: the base template (named via --as) or a legacy slug ──
|
|
392
|
+
const as = getFlag(args, '--as')
|
|
393
|
+
if (slug === 'base' && !as) {
|
|
394
|
+
die('The base template needs a name for the new agent:\n' +
|
|
395
|
+
' 0101-agents install base --as <name>')
|
|
396
|
+
}
|
|
397
|
+
const destName = as || slug
|
|
235
398
|
const force = args.includes('--force')
|
|
236
399
|
|
|
237
|
-
|
|
238
|
-
const installDir = agentInstallDir(agent)
|
|
400
|
+
const installDir = agentInstallDir(destName)
|
|
239
401
|
if (existsSync(installDir) && !force) {
|
|
240
402
|
die(`${installDir} already exists. Use --force to overwrite (will wipe data/).`)
|
|
241
403
|
}
|
|
242
404
|
|
|
243
|
-
info(`Fetching manifest for ${
|
|
244
|
-
const manifest = await fetchManifest(
|
|
405
|
+
info(`Fetching manifest for ${slug}…`)
|
|
406
|
+
const manifest = await fetchManifest(slug)
|
|
245
407
|
info(`Latest: ${manifest.latest}`)
|
|
246
408
|
|
|
247
|
-
const zipPath = join(tmpdir(), `0101-${
|
|
409
|
+
const zipPath = join(tmpdir(), `0101-${slug}-${manifest.latest}.zip`)
|
|
248
410
|
info(`Downloading…`)
|
|
249
411
|
await downloadZip(manifest.download_url, zipPath)
|
|
250
412
|
|
|
@@ -254,32 +416,75 @@ async function cmdInstall(args) {
|
|
|
254
416
|
mkdirSync(installDir, { recursive: true })
|
|
255
417
|
unzipInto(zipPath, installDir)
|
|
256
418
|
rmSync(zipPath, { force: true })
|
|
257
|
-
const localManifest = readManifest(
|
|
258
|
-
runPostInstall(
|
|
259
|
-
writeInstalledVersion(
|
|
419
|
+
const localManifest = readManifest(destName)
|
|
420
|
+
runPostInstall(destName, localManifest)
|
|
421
|
+
writeInstalledVersion(destName, manifest.latest)
|
|
260
422
|
|
|
261
423
|
console.log('')
|
|
262
|
-
ok(`Installed ${bold(
|
|
263
|
-
info(`
|
|
264
|
-
|
|
265
|
-
// custom launch (manifest.start, e.g. the runner) aren't opened in Claude.
|
|
266
|
-
if (!localManifest.start) info(` or: cd ${installDir} && claude`)
|
|
424
|
+
ok(`Installed ${bold(destName)} ${manifest.latest} → ${installDir}`)
|
|
425
|
+
info(`Open it: cd ${installDir} && claude`)
|
|
426
|
+
if (as) info(`Add capabilities: 0101-agents install <set> --to ${destName}`)
|
|
267
427
|
}
|
|
268
428
|
|
|
269
429
|
async function cmdUpdate(args) {
|
|
270
|
-
const
|
|
430
|
+
const slug = args[0] && !args[0].startsWith('-') ? args[0] : null
|
|
271
431
|
const assumeYes = args.some((a) => ['-f', '--force', '-y', '--yes'].includes(a))
|
|
272
|
-
if (!
|
|
273
|
-
|
|
432
|
+
if (!slug) {
|
|
433
|
+
die('Usage: 0101-agents update <set> --to <agent>\n' +
|
|
434
|
+
' 0101-agents update <agent> [-f]')
|
|
435
|
+
}
|
|
436
|
+
rejectSelfManaged(slug, 'updated')
|
|
274
437
|
ensureUnzipAvailable()
|
|
438
|
+
|
|
439
|
+
// ── set inside an agent ──
|
|
440
|
+
const to = getFlag(args, '--to')
|
|
441
|
+
if (to) {
|
|
442
|
+
const sets = readSetsFile(to)
|
|
443
|
+
const current = setVersion(sets, slug)
|
|
444
|
+
if (!current) {
|
|
445
|
+
die(`${slug} is not installed in ${to}.\n` +
|
|
446
|
+
` Install it: 0101-agents install ${slug} --to ${to}`)
|
|
447
|
+
}
|
|
448
|
+
info(`Installed: ${current}`)
|
|
449
|
+
const manifest = await fetchManifest(slug)
|
|
450
|
+
info(`Latest: ${manifest.latest}`)
|
|
451
|
+
if (current === manifest.latest) {
|
|
452
|
+
ok('Already up to date.')
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
const v = await installSetInto(slug, to)
|
|
456
|
+
console.log('')
|
|
457
|
+
ok(`Updated set ${bold(slug)} ${current} → ${v} in ${to}`)
|
|
458
|
+
info('Updated skills load at the agent’s next session start.')
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ── whole agent: update its template, preserving data/ ──
|
|
463
|
+
// The remote slug comes from the installed manifest.json (a base-template
|
|
464
|
+
// agent named "ada" still updates from the "base" artifact). Falls back to
|
|
465
|
+
// the folder name for legacy installs.
|
|
466
|
+
//
|
|
467
|
+
// Discontinued persona agents migrate onto the base template on their next
|
|
468
|
+
// update: data/ (all memory) is preserved as always, the persona CLAUDE.md
|
|
469
|
+
// is replaced by the base skeleton, and the agent re-introduces itself in
|
|
470
|
+
// its next conversation. After migration the folder's manifest says "base",
|
|
471
|
+
// so every later update is a normal base update.
|
|
472
|
+
const LEGACY_TO_BASE = new Set(['marketer', 'developer', 'assistant', 'custom'])
|
|
473
|
+
const agent = slug
|
|
275
474
|
const installDir = agentInstallDir(agent)
|
|
276
|
-
if (!existsSync(installDir)) die(`${agent} is not installed
|
|
475
|
+
if (!existsSync(installDir)) die(`${agent} is not installed.`)
|
|
476
|
+
let remoteSlug = readManifest(agent)?.name || agent
|
|
477
|
+
if (LEGACY_TO_BASE.has(remoteSlug)) {
|
|
478
|
+
info(`${remoteSlug} is discontinued — migrating ${agent} to the base template.`)
|
|
479
|
+
info('All memory (data/) is preserved; the agent re-introduces itself next chat.')
|
|
480
|
+
remoteSlug = 'base'
|
|
481
|
+
}
|
|
277
482
|
|
|
278
483
|
const current = installedVersion(agent)
|
|
279
|
-
info(`Installed: ${current ?? 'unknown'}`)
|
|
484
|
+
info(`Installed: ${current ?? 'unknown'} (template: ${remoteSlug})`)
|
|
280
485
|
|
|
281
486
|
info(`Fetching manifest…`)
|
|
282
|
-
const manifest = await fetchManifest(
|
|
487
|
+
const manifest = await fetchManifest(remoteSlug)
|
|
283
488
|
info(`Latest: ${manifest.latest}`)
|
|
284
489
|
|
|
285
490
|
if (current === manifest.latest) {
|
|
@@ -319,72 +524,75 @@ function cmdList() {
|
|
|
319
524
|
const installed = listInstalled()
|
|
320
525
|
if (installed.length === 0) {
|
|
321
526
|
info('No agents installed.')
|
|
322
|
-
info(`
|
|
527
|
+
info(`Create one: 0101-agents install base --as <name>`)
|
|
323
528
|
return
|
|
324
529
|
}
|
|
325
530
|
for (const name of installed) {
|
|
326
531
|
const v = installedVersion(name) ?? dim('?')
|
|
327
532
|
console.log(` ${bold(name)} ${dim(v)}`)
|
|
533
|
+
const sets = readSetsFile(name)
|
|
534
|
+
for (const [set, rec] of Object.entries(sets)) {
|
|
535
|
+
console.log(` ${dim('· skillset')} ${set} ${dim(rec.version)}`)
|
|
536
|
+
}
|
|
328
537
|
}
|
|
329
538
|
}
|
|
330
539
|
|
|
331
540
|
async function cmdStart(args) {
|
|
332
541
|
const agent = args[0]
|
|
333
|
-
if (!agent) die('Usage: 0101-agents start <agent> [-- <claude-args>]')
|
|
542
|
+
if (!agent) die('Usage: 0101-agents start <agent> [--tg] [-- <claude-args>]')
|
|
334
543
|
const dir = agentInstallDir(agent)
|
|
335
544
|
if (!existsSync(dir)) {
|
|
336
|
-
die(`${agent} is not installed
|
|
545
|
+
die(`${agent} is not installed.`)
|
|
337
546
|
}
|
|
338
547
|
|
|
339
|
-
// Best-effort update
|
|
340
|
-
//
|
|
548
|
+
// Best-effort update nag. Needs a saved key (the releases API is gated);
|
|
549
|
+
// no key, offline, or any non-200 (revoked, lapsed, artifact not on the
|
|
550
|
+
// server) → skip silently. Launching NEVER depends on this — deliberately
|
|
551
|
+
// not fetchManifest(), whose die() on HTTP errors would kill the launch.
|
|
341
552
|
try {
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
)
|
|
553
|
+
const key = readConfig().license_key
|
|
554
|
+
if (key) {
|
|
555
|
+
const remoteSlug = readManifest(agent)?.name || agent
|
|
556
|
+
const res = await fetch(`${apiBase()}/api/releases/${encodeURIComponent(remoteSlug)}`, {
|
|
557
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
558
|
+
})
|
|
559
|
+
if (res.ok) {
|
|
560
|
+
const manifest = await res.json()
|
|
561
|
+
const current = installedVersion(agent)
|
|
562
|
+
if (current && manifest.latest !== current) {
|
|
563
|
+
console.log(
|
|
564
|
+
dim(`⚠ ${agent} ${manifest.latest} available (run: 0101-agents update ${agent})`),
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
348
568
|
}
|
|
349
569
|
} catch {
|
|
350
570
|
// ignore
|
|
351
571
|
}
|
|
352
572
|
|
|
353
573
|
// Artifacts can declare how `start` launches via manifest.start. The runner
|
|
354
|
-
// sets "npm start"; agents leave it unset and fall through
|
|
355
|
-
// Claude Code below.
|
|
356
|
-
//
|
|
357
|
-
// For the runner specifically we accept an optional `[slug] [cli]` so
|
|
358
|
-
// power users can run multiple bots in parallel terminals without touching
|
|
359
|
-
// .env. The runner derives STATE_DIR from AGENT_DIR, so per-agent state is
|
|
360
|
-
// automatic — we only need to override AGENT_DIR here.
|
|
574
|
+
// (fallback engine) sets "npm start"; agents leave it unset and fall through
|
|
575
|
+
// to launching Claude Code below.
|
|
361
576
|
//
|
|
362
577
|
// start runner → npm start (Telegram, AGENT_DIR from .env)
|
|
363
|
-
// start runner cli → npm start -- --cli (CLI, AGENT_DIR from .env)
|
|
364
|
-
// start runner
|
|
365
|
-
// start runner
|
|
366
|
-
// start runner --cli → same as `start runner cli` (long-form flag)
|
|
367
|
-
// start runner -- --cli → same (explicit separator)
|
|
578
|
+
// start runner cli → npm start -- --cli (CLI chat, AGENT_DIR from .env)
|
|
579
|
+
// start runner <name> → npm start (Telegram for that agent)
|
|
580
|
+
// start runner <name> cli → npm start -- --cli
|
|
368
581
|
const startCmd = readManifest(agent).start
|
|
369
582
|
if (startCmd) {
|
|
370
583
|
let extra = args.slice(1)
|
|
371
584
|
if (extra[0] === '--') extra = extra.slice(1)
|
|
372
585
|
|
|
373
|
-
// Optional [
|
|
374
|
-
// (no real agent will ever be named "cli"). Anything else that doesn't
|
|
375
|
-
// start with `-` is treated as a slug. Verified installed before we spawn.
|
|
586
|
+
// Optional [name] [mode] positionals — `cli` is a reserved mode keyword.
|
|
376
587
|
let slug = null
|
|
377
588
|
if (extra[0] && extra[0] !== 'cli' && !extra[0].startsWith('-')) {
|
|
378
589
|
slug = extra.shift()
|
|
379
590
|
if (!existsSync(agentInstallDir(slug))) {
|
|
380
|
-
die(`${slug} is not installed
|
|
591
|
+
die(`${slug} is not installed.`)
|
|
381
592
|
}
|
|
382
593
|
}
|
|
383
594
|
extra = extra.map(a => (a === 'cli' ? '--cli' : a))
|
|
384
595
|
|
|
385
|
-
// Override AGENT_DIR per invocation when a slug is given. The runner's
|
|
386
|
-
// config.ts derives STATE_DIR from basename(AGENT_DIR), so the per-agent
|
|
387
|
-
// state/lockfile/incoming dirs follow automatically.
|
|
388
596
|
const childEnv = { ...process.env }
|
|
389
597
|
if (slug) {
|
|
390
598
|
childEnv.AGENT_DIR = agentInstallDir(slug)
|
|
@@ -398,10 +606,7 @@ async function cmdStart(args) {
|
|
|
398
606
|
return
|
|
399
607
|
}
|
|
400
608
|
|
|
401
|
-
// Everything after the agent name passes through to `claude`.
|
|
402
|
-
// 0101-agents start marketer --resume
|
|
403
|
-
// 0101-agents start marketer -- --resume (explicit separator)
|
|
404
|
-
// The `--` is optional; if present, we drop it.
|
|
609
|
+
// Everything after the agent name passes through to `claude`.
|
|
405
610
|
let rest = args.slice(1)
|
|
406
611
|
if (rest[0] === '--') rest = rest.slice(1)
|
|
407
612
|
|
|
@@ -419,8 +624,7 @@ async function cmdStart(args) {
|
|
|
419
624
|
]
|
|
420
625
|
}
|
|
421
626
|
|
|
422
|
-
// Hand the terminal over to `claude` with cwd set to the agent dir.
|
|
423
|
-
// claude exits, the user returns to their original shell location.
|
|
627
|
+
// Hand the terminal over to `claude` with cwd set to the agent dir.
|
|
424
628
|
const child = spawn('claude', claudeArgs, { cwd: dir, stdio: 'inherit' })
|
|
425
629
|
child.on('error', (e) => {
|
|
426
630
|
if (e.code === 'ENOENT') {
|
|
@@ -432,28 +636,28 @@ async function cmdStart(args) {
|
|
|
432
636
|
}
|
|
433
637
|
|
|
434
638
|
function cmdHelp() {
|
|
435
|
-
console.log(`0101-agents — install and update
|
|
639
|
+
console.log(`0101-agents — install and update 0101 skillsets and agent templates.
|
|
436
640
|
|
|
437
641
|
Commands:
|
|
438
|
-
${bold('login')} [<key>]
|
|
439
|
-
${bold('logout')}
|
|
440
|
-
${bold('whoami')}
|
|
441
|
-
${bold('install')} <
|
|
442
|
-
${bold('
|
|
443
|
-
${bold('
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
${bold('
|
|
642
|
+
${bold('login')} [<key>] Save your license key
|
|
643
|
+
${bold('logout')} Forget your license key
|
|
644
|
+
${bold('whoami')} Show current license
|
|
645
|
+
${bold('install')} <set> --to <agent> Install a skillset into an agent
|
|
646
|
+
${bold('install')} base --as <name> Create a new agent from the base template
|
|
647
|
+
${bold('update')} <set> --to <agent> Update a skillset inside an agent
|
|
648
|
+
${bold('update')} <agent> [-f] Update an agent's template (preserves data/)
|
|
649
|
+
${bold('start')} <agent> [--tg] [-- args] Open the agent (runs claude in its dir);
|
|
650
|
+
--tg runs it as a Telegram channel bot
|
|
651
|
+
${bold('list')} List installed agents and their skillsets
|
|
652
|
+
${bold('help')} Show this message
|
|
448
653
|
|
|
449
654
|
Examples:
|
|
450
|
-
0101-agents
|
|
451
|
-
0101-agents
|
|
452
|
-
0101-agents
|
|
453
|
-
0101-agents
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
0101-agents start runner marketer cli Terminal chat for marketer
|
|
655
|
+
0101-agents install base --as ada New agent named "ada"
|
|
656
|
+
0101-agents install website-studio --to ada Teach ada website skills
|
|
657
|
+
0101-agents update website-studio --to ada Refresh that skillset
|
|
658
|
+
0101-agents update ada Update ada's base template
|
|
659
|
+
|
|
660
|
+
Open an agent: cd ${INSTALL_ROOT}/<name> && claude
|
|
457
661
|
|
|
458
662
|
Install dir: ${INSTALL_ROOT}
|
|
459
663
|
Config: ${CONFIG_PATH}`)
|