0101-agents 0.1.2 → 0.1.5
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 +101 -34
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 0101-agents logout
|
|
7
7
|
// 0101-agents whoami
|
|
8
8
|
// 0101-agents install <agent> [--force]
|
|
9
|
-
// 0101-agents update <agent>
|
|
9
|
+
// 0101-agents update <agent> [-f]
|
|
10
10
|
// 0101-agents list
|
|
11
11
|
// 0101-agents help
|
|
12
12
|
|
|
@@ -131,31 +131,64 @@ function ensureUnzipAvailable() {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
function unzipInto(zipPath, destDir, {
|
|
134
|
+
function unzipInto(zipPath, destDir, { exclude = [] } = {}) {
|
|
135
135
|
// Use the system unzip binary. Available everywhere except bare Windows.
|
|
136
|
+
// -o overwrites; a single -x takes the whole pattern list (after -d), used on
|
|
137
|
+
// update to keep the manifest's `preserve` paths (e.g. data/, .env) intact.
|
|
136
138
|
mkdirSync(destDir, { recursive: true })
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
const xs = exclude.length ? `-x ${exclude.map((p) => `"${p}"`).join(' ')}` : ''
|
|
140
|
+
execSync(`unzip -q -o "${zipPath}" -d "${destDir}" ${xs}`.trim(), { stdio: 'inherit' })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Read the installed artifact's own manifest.json (NOT the release manifest
|
|
144
|
+
// from the API). Drives per-artifact behavior: which paths to keep on update
|
|
145
|
+
// (`preserve`), an optional post-install command (`postInstall`), and how
|
|
146
|
+
// `start` launches. Fails soft to {} so agents without these fields behave
|
|
147
|
+
// exactly as before.
|
|
148
|
+
function readManifest(agent) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(readFileSync(join(agentInstallDir(agent), 'manifest.json'), 'utf8'))
|
|
151
|
+
} catch {
|
|
152
|
+
return {}
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
// Paths an update must NOT overwrite. Agents keep their data/ by default; the
|
|
157
|
+
// runner declares [".env", ".runner-state"]. Always returns a non-empty array.
|
|
158
|
+
function preserveList(manifest) {
|
|
159
|
+
const p = manifest?.preserve
|
|
160
|
+
return Array.isArray(p) && p.length ? p : ['data']
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// unzip -x patterns: drop both the entry and everything under it, so a
|
|
164
|
+
// preserved file (.env) and a preserved dir (data/) are each left untouched.
|
|
165
|
+
function excludePatterns(preserve) {
|
|
166
|
+
return preserve.flatMap((p) => [p, `${p}/*`])
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Run the manifest's postInstall command (if any) in the install dir — e.g. the
|
|
170
|
+
// runner's `npm ci && node scripts/postinstall.mjs`. Trusted: the zip is the
|
|
171
|
+
// license-gated artifact we shipped. No-op for agents (no postInstall field).
|
|
172
|
+
function runPostInstall(agent, manifest) {
|
|
173
|
+
const cmd = manifest?.postInstall
|
|
174
|
+
if (!cmd || typeof cmd !== 'string') return
|
|
175
|
+
info('Running post-install…')
|
|
176
|
+
execSync(cmd, { cwd: agentInstallDir(agent), stdio: 'inherit' })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Back up the paths an update would replace, so a bad release is recoverable.
|
|
180
|
+
// Tars only the preserve paths that actually exist (data/ for agents; .env +
|
|
181
|
+
// .runner-state/ for the runner).
|
|
182
|
+
function backupPreserved(agent, preserve) {
|
|
183
|
+
const dir = agentInstallDir(agent)
|
|
184
|
+
const existing = preserve.filter((p) => existsSync(join(dir, p)))
|
|
185
|
+
if (!existing.length) return null
|
|
153
186
|
mkdirSync(BACKUPS_DIR, { recursive: true })
|
|
154
187
|
const ts = new Date().toISOString().replace(/[:.]/g, '-')
|
|
155
188
|
const oldVersion = installedVersion(agent) ?? 'unknown'
|
|
156
189
|
const backupPath = join(BACKUPS_DIR, `${agent}-${oldVersion}-${ts}.tar.gz`)
|
|
157
190
|
execSync(
|
|
158
|
-
`tar -czf "${backupPath}" -C "${
|
|
191
|
+
`tar -czf "${backupPath}" -C "${dir}" ${existing.map((p) => `"${p}"`).join(' ')}`,
|
|
159
192
|
{ stdio: 'inherit' },
|
|
160
193
|
)
|
|
161
194
|
return backupPath
|
|
@@ -220,8 +253,9 @@ async function cmdInstall(args) {
|
|
|
220
253
|
}
|
|
221
254
|
mkdirSync(installDir, { recursive: true })
|
|
222
255
|
unzipInto(zipPath, installDir)
|
|
223
|
-
writeInstalledVersion(agent, manifest.latest)
|
|
224
256
|
rmSync(zipPath, { force: true })
|
|
257
|
+
runPostInstall(agent, readManifest(agent))
|
|
258
|
+
writeInstalledVersion(agent, manifest.latest)
|
|
225
259
|
|
|
226
260
|
console.log('')
|
|
227
261
|
ok(`Installed ${bold(agent)} ${manifest.latest} → ${installDir}`)
|
|
@@ -230,8 +264,9 @@ async function cmdInstall(args) {
|
|
|
230
264
|
}
|
|
231
265
|
|
|
232
266
|
async function cmdUpdate(args) {
|
|
233
|
-
const agent = args
|
|
234
|
-
|
|
267
|
+
const agent = args.find((a) => !a.startsWith('-'))
|
|
268
|
+
const assumeYes = args.some((a) => ['-f', '--force', '-y', '--yes'].includes(a))
|
|
269
|
+
if (!agent) die('Usage: 0101-agents update <agent> [-f]')
|
|
235
270
|
|
|
236
271
|
ensureUnzipAvailable()
|
|
237
272
|
const installDir = agentInstallDir(agent)
|
|
@@ -249,25 +284,29 @@ async function cmdUpdate(args) {
|
|
|
249
284
|
return
|
|
250
285
|
}
|
|
251
286
|
|
|
252
|
-
console.log(dim('Your data
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
287
|
+
console.log(dim('Your local data is preserved across the update.'))
|
|
288
|
+
if (!assumeYes) {
|
|
289
|
+
const ans = await prompt(`Update ${agent} ${current ?? '?'} → ${manifest.latest}? [Y/n] `)
|
|
290
|
+
if (ans && !/^y(es)?$/i.test(ans)) {
|
|
291
|
+
info('Cancelled.')
|
|
292
|
+
return
|
|
293
|
+
}
|
|
257
294
|
}
|
|
258
295
|
|
|
259
|
-
const
|
|
260
|
-
|
|
296
|
+
const preserve = preserveList(readManifest(agent))
|
|
297
|
+
const backup = backupPreserved(agent, preserve)
|
|
298
|
+
if (backup) info(`Backed up ${preserve.join(', ')} → ${backup}`)
|
|
261
299
|
|
|
262
300
|
const zipPath = join(tmpdir(), `0101-${agent}-${manifest.latest}.zip`)
|
|
263
301
|
info(`Downloading…`)
|
|
264
302
|
await downloadZip(manifest.download_url, zipPath)
|
|
265
303
|
|
|
266
|
-
// Extract
|
|
267
|
-
// survives
|
|
268
|
-
unzipInto(zipPath, installDir, {
|
|
269
|
-
writeInstalledVersion(agent, manifest.latest)
|
|
304
|
+
// Extract over the install, skipping the manifest's `preserve` paths so user
|
|
305
|
+
// state survives (agents: data/; runner: .env + .runner-state/).
|
|
306
|
+
unzipInto(zipPath, installDir, { exclude: excludePatterns(preserve) })
|
|
270
307
|
rmSync(zipPath, { force: true })
|
|
308
|
+
runPostInstall(agent, readManifest(agent))
|
|
309
|
+
writeInstalledVersion(agent, manifest.latest)
|
|
271
310
|
|
|
272
311
|
console.log('')
|
|
273
312
|
ok(`Updated ${bold(agent)} → ${manifest.latest}`)
|
|
@@ -308,12 +347,37 @@ async function cmdStart(args) {
|
|
|
308
347
|
// ignore
|
|
309
348
|
}
|
|
310
349
|
|
|
350
|
+
// Artifacts can declare how `start` launches via manifest.start. The runner
|
|
351
|
+
// sets "npm start"; agents leave it unset and fall through to launching
|
|
352
|
+
// Claude Code below.
|
|
353
|
+
const startCmd = readManifest(agent).start
|
|
354
|
+
if (startCmd) {
|
|
355
|
+
const child = spawn(startCmd, { cwd: dir, stdio: 'inherit', shell: true })
|
|
356
|
+
child.on('error', (e) => die(`Failed to start: ${e.message}`))
|
|
357
|
+
child.on('exit', (code) => process.exit(code ?? 0))
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
311
361
|
// Everything after the agent name passes through to `claude`. Supports both:
|
|
312
362
|
// 0101-agents start marketer --resume
|
|
313
363
|
// 0101-agents start marketer -- --resume (explicit separator)
|
|
314
364
|
// The `--` is optional; if present, we drop it.
|
|
315
|
-
let
|
|
316
|
-
if (
|
|
365
|
+
let rest = args.slice(1)
|
|
366
|
+
if (rest[0] === '--') rest = rest.slice(1)
|
|
367
|
+
|
|
368
|
+
// --tg expands to the flags needed to run as a Telegram channel bot.
|
|
369
|
+
// Keeping this in the CLI (not in shell wrappers) means flag changes
|
|
370
|
+
// ship via 'npm i -g 0101-agents@latest' instead of per-server re-installs.
|
|
371
|
+
const tgIndex = rest.indexOf('--tg')
|
|
372
|
+
let claudeArgs = rest
|
|
373
|
+
if (tgIndex !== -1) {
|
|
374
|
+
claudeArgs = [
|
|
375
|
+
...rest.slice(0, tgIndex),
|
|
376
|
+
'--channels', 'plugin:telegram@claude-plugins-official',
|
|
377
|
+
'--dangerously-skip-permissions',
|
|
378
|
+
...rest.slice(tgIndex + 1),
|
|
379
|
+
]
|
|
380
|
+
}
|
|
317
381
|
|
|
318
382
|
// Hand the terminal over to `claude` with cwd set to the agent dir. When
|
|
319
383
|
// claude exits, the user returns to their original shell location.
|
|
@@ -335,13 +399,16 @@ Commands:
|
|
|
335
399
|
${bold('logout')} Forget your license key
|
|
336
400
|
${bold('whoami')} Show current license
|
|
337
401
|
${bold('install')} <agent> [--force] Install an agent
|
|
338
|
-
${bold('update')} <agent>
|
|
339
|
-
${bold('start')} <agent> [-- args]
|
|
402
|
+
${bold('update')} <agent> [-f] Update an installed agent (preserves data/). -f skips the prompt
|
|
403
|
+
${bold('start')} <agent> [--tg] [-- args] Open the agent (runs claude in its dir)
|
|
404
|
+
--tg adds the right flags to run as a
|
|
405
|
+
Telegram channel bot.
|
|
340
406
|
${bold('list')} List installed agents
|
|
341
407
|
${bold('help')} Show this message
|
|
342
408
|
|
|
343
409
|
Examples:
|
|
344
410
|
0101-agents start marketer
|
|
411
|
+
0101-agents start marketer --tg
|
|
345
412
|
0101-agents start marketer --resume
|
|
346
413
|
0101-agents start marketer -- -p "audit https://example.com"
|
|
347
414
|
|