0101-agents 0.1.3 → 0.1.6
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 +85 -32
- 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,18 +253,23 @@ 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
|
+
const localManifest = readManifest(agent)
|
|
258
|
+
runPostInstall(agent, localManifest)
|
|
259
|
+
writeInstalledVersion(agent, manifest.latest)
|
|
225
260
|
|
|
226
261
|
console.log('')
|
|
227
262
|
ok(`Installed ${bold(agent)} ${manifest.latest} → ${installDir}`)
|
|
228
263
|
info(`Start: 0101-agents start ${agent}`)
|
|
229
|
-
|
|
264
|
+
// The "open in Claude" hint only applies to agent folders. Artifacts with a
|
|
265
|
+
// custom launch (manifest.start, e.g. the runner) aren't opened in Claude.
|
|
266
|
+
if (!localManifest.start) info(` or: cd ${installDir} && claude`)
|
|
230
267
|
}
|
|
231
268
|
|
|
232
269
|
async function cmdUpdate(args) {
|
|
233
|
-
const agent = args
|
|
234
|
-
|
|
270
|
+
const agent = args.find((a) => !a.startsWith('-'))
|
|
271
|
+
const assumeYes = args.some((a) => ['-f', '--force', '-y', '--yes'].includes(a))
|
|
272
|
+
if (!agent) die('Usage: 0101-agents update <agent> [-f]')
|
|
235
273
|
|
|
236
274
|
ensureUnzipAvailable()
|
|
237
275
|
const installDir = agentInstallDir(agent)
|
|
@@ -249,25 +287,29 @@ async function cmdUpdate(args) {
|
|
|
249
287
|
return
|
|
250
288
|
}
|
|
251
289
|
|
|
252
|
-
console.log(dim('Your data
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
290
|
+
console.log(dim('Your local data is preserved across the update.'))
|
|
291
|
+
if (!assumeYes) {
|
|
292
|
+
const ans = await prompt(`Update ${agent} ${current ?? '?'} → ${manifest.latest}? [Y/n] `)
|
|
293
|
+
if (ans && !/^y(es)?$/i.test(ans)) {
|
|
294
|
+
info('Cancelled.')
|
|
295
|
+
return
|
|
296
|
+
}
|
|
257
297
|
}
|
|
258
298
|
|
|
259
|
-
const
|
|
260
|
-
|
|
299
|
+
const preserve = preserveList(readManifest(agent))
|
|
300
|
+
const backup = backupPreserved(agent, preserve)
|
|
301
|
+
if (backup) info(`Backed up ${preserve.join(', ')} → ${backup}`)
|
|
261
302
|
|
|
262
303
|
const zipPath = join(tmpdir(), `0101-${agent}-${manifest.latest}.zip`)
|
|
263
304
|
info(`Downloading…`)
|
|
264
305
|
await downloadZip(manifest.download_url, zipPath)
|
|
265
306
|
|
|
266
|
-
// Extract
|
|
267
|
-
// survives
|
|
268
|
-
unzipInto(zipPath, installDir, {
|
|
269
|
-
writeInstalledVersion(agent, manifest.latest)
|
|
307
|
+
// Extract over the install, skipping the manifest's `preserve` paths so user
|
|
308
|
+
// state survives (agents: data/; runner: .env + .runner-state/).
|
|
309
|
+
unzipInto(zipPath, installDir, { exclude: excludePatterns(preserve) })
|
|
270
310
|
rmSync(zipPath, { force: true })
|
|
311
|
+
runPostInstall(agent, readManifest(agent))
|
|
312
|
+
writeInstalledVersion(agent, manifest.latest)
|
|
271
313
|
|
|
272
314
|
console.log('')
|
|
273
315
|
ok(`Updated ${bold(agent)} → ${manifest.latest}`)
|
|
@@ -308,6 +350,17 @@ async function cmdStart(args) {
|
|
|
308
350
|
// ignore
|
|
309
351
|
}
|
|
310
352
|
|
|
353
|
+
// Artifacts can declare how `start` launches via manifest.start. The runner
|
|
354
|
+
// sets "npm start"; agents leave it unset and fall through to launching
|
|
355
|
+
// Claude Code below.
|
|
356
|
+
const startCmd = readManifest(agent).start
|
|
357
|
+
if (startCmd) {
|
|
358
|
+
const child = spawn(startCmd, { cwd: dir, stdio: 'inherit', shell: true })
|
|
359
|
+
child.on('error', (e) => die(`Failed to start: ${e.message}`))
|
|
360
|
+
child.on('exit', (code) => process.exit(code ?? 0))
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
311
364
|
// Everything after the agent name passes through to `claude`. Supports both:
|
|
312
365
|
// 0101-agents start marketer --resume
|
|
313
366
|
// 0101-agents start marketer -- --resume (explicit separator)
|
|
@@ -349,7 +402,7 @@ Commands:
|
|
|
349
402
|
${bold('logout')} Forget your license key
|
|
350
403
|
${bold('whoami')} Show current license
|
|
351
404
|
${bold('install')} <agent> [--force] Install an agent
|
|
352
|
-
${bold('update')} <agent>
|
|
405
|
+
${bold('update')} <agent> [-f] Update an installed agent (preserves data/). -f skips the prompt
|
|
353
406
|
${bold('start')} <agent> [--tg] [-- args] Open the agent (runs claude in its dir)
|
|
354
407
|
--tg adds the right flags to run as a
|
|
355
408
|
Telegram channel bot.
|