@agentage/cli 0.38.0 → 0.39.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.
Files changed (62) hide show
  1. package/dist/cli.js +5 -3
  2. package/dist/commands/auth/setup.d.ts +6 -14
  3. package/dist/commands/auth/setup.js +9 -121
  4. package/dist/commands/plugins/status-print.d.ts +2 -0
  5. package/dist/commands/plugins/status-print.js +44 -0
  6. package/dist/commands/plugins/status-rows.d.ts +51 -0
  7. package/dist/commands/plugins/status-rows.js +58 -0
  8. package/dist/commands/status/status.d.ts +9 -3
  9. package/dist/commands/status/status.js +53 -8
  10. package/dist/commands/update/update.d.ts +3 -1
  11. package/dist/commands/update/update.js +25 -22
  12. package/dist/core/loader/discover.d.ts +33 -0
  13. package/dist/core/loader/discover.js +103 -0
  14. package/dist/core/loader/manifest.d.ts +16 -0
  15. package/dist/core/loader/manifest.js +66 -0
  16. package/dist/core/loader/state.d.ts +12 -0
  17. package/dist/core/loader/state.js +21 -0
  18. package/dist/core/loader/states.d.ts +7 -0
  19. package/dist/core/loader/states.js +52 -0
  20. package/dist/core/paths.d.ts +4 -0
  21. package/dist/core/paths.js +6 -0
  22. package/dist/core/profile/machine.d.ts +18 -0
  23. package/dist/core/profile/machine.js +60 -0
  24. package/dist/core/profile/merge.d.ts +11 -0
  25. package/dist/core/profile/merge.js +16 -0
  26. package/dist/core/profile/profile.d.ts +6 -0
  27. package/dist/core/profile/profile.js +34 -0
  28. package/dist/core/profile/schema.d.ts +26 -0
  29. package/dist/core/profile/schema.js +143 -0
  30. package/dist/core/semver-range.d.ts +16 -0
  31. package/dist/core/semver-range.js +116 -0
  32. package/dist/lib/auth/api.d.ts +3 -2
  33. package/dist/lib/auth/api.js +50 -56
  34. package/dist/lib/auth/callback-page.d.ts +2 -0
  35. package/dist/lib/auth/callback-page.js +40 -0
  36. package/dist/lib/auth/callback-server.js +1 -4
  37. package/dist/lib/auth/env-match.js +1 -0
  38. package/dist/lib/auth/introspect.js +10 -11
  39. package/dist/lib/auth/oauth.d.ts +2 -0
  40. package/dist/lib/auth/oauth.js +30 -28
  41. package/dist/lib/auth/sign-in.d.ts +20 -0
  42. package/dist/lib/auth/sign-in.js +95 -0
  43. package/dist/lib/fs/config.d.ts +1 -0
  44. package/dist/lib/fs/config.js +14 -21
  45. package/dist/lib/fs/file-lock.d.ts +7 -2
  46. package/dist/lib/fs/file-lock.js +64 -74
  47. package/dist/lib/fs/json-file.d.ts +3 -0
  48. package/dist/lib/fs/json-file.js +43 -0
  49. package/dist/lib/net/fetch.d.ts +10 -0
  50. package/dist/lib/net/fetch.js +13 -0
  51. package/dist/lib/net/http.js +51 -23
  52. package/dist/lib/npm/npm-cli.d.ts +17 -0
  53. package/dist/lib/npm/npm-cli.js +77 -0
  54. package/dist/lib/status/status-info.js +1 -0
  55. package/dist/lib/update/update-cache.d.ts +1 -0
  56. package/dist/lib/update/update-cache.js +7 -11
  57. package/dist/lib/update/update-check.js +1 -4
  58. package/dist/program.d.ts +18 -1
  59. package/dist/program.js +75 -32
  60. package/package.json +1 -1
  61. package/dist/lib/update/update-lock.d.ts +0 -2
  62. package/dist/lib/update/update-lock.js +0 -109
package/dist/program.js CHANGED
@@ -1,38 +1,81 @@
1
1
  import chalk from 'chalk';
2
- import { Command } from 'commander';
3
- import { registerSetup } from './commands/auth/setup.js';
4
- import { registerStatus } from './commands/status/status.js';
5
- import { registerUpdate } from './commands/update/update.js';
2
+ import { Command, CommanderError } from 'commander';
3
+ import { registerSetup, runSetup } from './commands/auth/setup.js';
4
+ import { registerStatus, runStatus } from './commands/status/status.js';
5
+ import { registerUpdate, runUpdate } from './commands/update/update.js';
6
+ import { ensureSignedIn, signOut } from './lib/auth/sign-in.js';
6
7
  import { refreshUpdateCache, updateHint } from './lib/update/update-cache.js';
7
8
  import { VERSION } from './utils/version.js';
8
- const program = new Command();
9
- program
10
- .name('agentage')
11
- .description('Connect this machine to agentage from the terminal')
12
- .version(VERSION);
13
- program.addHelpText('after', '\nNew here? Run: agentage setup to connect this machine.');
14
- // After any command (except the update-aware ones, which report richer state themselves), print a
15
- // one-line hint from the cache, then kick a fire-and-forget refresh. Never awaited, never throws -
16
- // the command output is already done and the cache-only read keeps this instant. The hint is
17
- // suppressed under --json so it never corrupts machine-readable output.
18
- program.hook('postAction', (_thisCommand, actionCommand) => {
19
- const name = actionCommand.name();
20
- if (name === 'status' || name === 'update')
9
+ const defaultDeps = {
10
+ setup: (opts) => runSetup(opts, {
11
+ ensureSignedIn: (signInOpts) => ensureSignedIn(signInOpts),
12
+ signOut: () => signOut(),
13
+ printStatus: () => runStatus({}),
14
+ }),
15
+ status: (opts) => runStatus(opts),
16
+ update: (opts) => runUpdate(opts),
17
+ updateHint,
18
+ refreshUpdateCache: () => refreshUpdateCache(),
19
+ writeOut: (text) => void process.stdout.write(text),
20
+ writeErr: (text) => void process.stderr.write(text),
21
+ };
22
+ export const EXIT_OK = 0;
23
+ export const EXIT_FAILED = 1;
24
+ export const EXIT_USAGE = 2;
25
+ const ACTION_FAILED = 'agentage.actionFailed';
26
+ const messageOf = (err) => (err instanceof Error ? err.message : String(err));
27
+ // One failure path for every command: a red stderr line, then exit 1 through run().
28
+ const withAction = (deps, fn, prefix = '') => async (opts) => {
29
+ try {
30
+ await fn(opts);
31
+ }
32
+ catch (err) {
33
+ deps.writeErr(`${chalk.red(`${prefix}${messageOf(err)}`)}\n`);
34
+ throw new CommanderError(EXIT_FAILED, ACTION_FAILED, messageOf(err));
35
+ }
36
+ };
37
+ // status and update report update state themselves; the hint never touches --json stdout.
38
+ const hintAfter = (deps) => (_program, action) => {
39
+ if (action.name() === 'status' || action.name() === 'update')
21
40
  return;
22
- if (!actionCommand.opts()['json']) {
23
- const hint = updateHint();
24
- if (hint)
25
- console.error(chalk.dim(hint)); // stderr: never corrupt piped stdout
41
+ const hint = action.opts()['json'] ? null : deps.updateHint();
42
+ if (hint)
43
+ deps.writeErr(`${chalk.dim(hint)}\n`);
44
+ void deps.refreshUpdateCache();
45
+ };
46
+ export const createProgram = (deps = defaultDeps) => {
47
+ const program = new Command()
48
+ .name('agentage')
49
+ .description('Connect this machine to agentage from the terminal')
50
+ .version(VERSION)
51
+ .exitOverride()
52
+ .configureOutput({ writeOut: deps.writeOut, writeErr: deps.writeErr })
53
+ .addHelpText('after', '\nNew here? Run: agentage setup to connect this machine.')
54
+ .hook('postAction', hintAfter(deps));
55
+ registerSetup(program, withAction(deps, deps.setup, 'Setup failed: '));
56
+ registerStatus(program, withAction(deps, deps.status));
57
+ registerUpdate(program, withAction(deps, deps.update));
58
+ return program;
59
+ };
60
+ // Commander's own exit 0 (--help, --version) stays 0; any other commander error is usage.
61
+ const exitCodeOf = (err, deps) => {
62
+ if (!(err instanceof CommanderError)) {
63
+ deps.writeErr(`${chalk.red(messageOf(err))}\n`);
64
+ return EXIT_FAILED;
65
+ }
66
+ if (err.code === ACTION_FAILED)
67
+ return EXIT_FAILED;
68
+ return err.exitCode === 0 ? EXIT_OK : EXIT_USAGE;
69
+ };
70
+ // Returns the exit code instead of exiting, so piped stdout is never cut short.
71
+ export const run = async (argv = process.argv, deps = defaultDeps) => {
72
+ if (process.env['NO_COLOR'])
73
+ chalk.level = 0; // chalk ignores NO_COLOR on its own
74
+ try {
75
+ await createProgram(deps).parseAsync(argv);
76
+ return EXIT_OK;
77
+ }
78
+ catch (err) {
79
+ return exitCodeOf(err, deps);
26
80
  }
27
- void refreshUpdateCache();
28
- });
29
- registerSetup(program);
30
- registerStatus(program);
31
- registerUpdate(program);
32
- // Called by the bin (src/cli.ts).
33
- export const run = async () => {
34
- await program.parseAsync().catch((err) => {
35
- console.error(err instanceof Error ? err.message : String(err));
36
- process.exit(1);
37
- });
38
81
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentage/cli",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "description": "The agentage CLI - connect this machine to agentage from the terminal",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,2 +0,0 @@
1
- export declare const acquireUpdateLock: (now?: number) => boolean;
2
- export declare const releaseUpdateLock: () => void;
@@ -1,109 +0,0 @@
1
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { ensureConfigDir, getConfigDir } from '../fs/config.js';
4
- const LOCK_FILE = 'update.lock';
5
- const LOCK_TTL_MS = 10 * 60 * 1000; // a lock older than this whose holder is gone is a crashed run
6
- // Past this age a holder is taken over even if its pid still resolves (pid reuse after a crash); far
7
- // beyond any real run or scheduler pause, so a live-but-slow holder is never mistaken for crashed.
8
- const STALE_HARD_MS = 30 * 60 * 1000;
9
- const lockPath = () => join(getConfigDir(), LOCK_FILE);
10
- // The lock file holds "<pid> <timestamp>": pid gates crash-takeover, the trailing token is the
11
- // acquisition time. A legacy single-token file parses as pid 0 (unknown holder), aging out on TTL.
12
- const holderOf = (path) => {
13
- try {
14
- const parts = readFileSync(path, 'utf-8').trim().split(/\s+/);
15
- const at = Number.parseInt(parts[parts.length - 1] ?? '', 10);
16
- if (Number.isNaN(at))
17
- return null;
18
- const pid = parts.length >= 2 ? Number.parseInt(parts[0] ?? '', 10) : 0;
19
- return { pid: Number.isNaN(pid) ? 0 : pid, at };
20
- }
21
- catch {
22
- return null;
23
- }
24
- };
25
- // Same-host liveness probe. ESRCH = the holder is gone (crashed); EPERM = alive but owned by another
26
- // user. pid 0/unknown counts as gone so a legacy or malformed lock still ages out on the TTL.
27
- const holderAlive = (pid) => {
28
- if (pid <= 0)
29
- return false;
30
- try {
31
- process.kill(pid, 0);
32
- return true;
33
- }
34
- catch (err) {
35
- return err.code === 'EPERM';
36
- }
37
- };
38
- // A holder is safe to take over only once it is past the TTL AND provably gone: a dead pid, our own
39
- // leftover pid, or so old that pid reuse is the only explanation. A live holder merely starved of
40
- // CPU is never stolen - stealing it would let two writers run concurrently.
41
- const isCrashed = (holder, now) => {
42
- const age = now - holder.at;
43
- if (age < LOCK_TTL_MS)
44
- return false;
45
- if (age >= STALE_HARD_MS)
46
- return true;
47
- return holder.pid === process.pid || !holderAlive(holder.pid);
48
- };
49
- const unlinkQuiet = (path) => {
50
- try {
51
- unlinkSync(path);
52
- }
53
- catch {
54
- // already gone
55
- }
56
- };
57
- // Serialize stale-lock takeover behind its own exclusive create, so no taker can ever delete a
58
- // lock that turned FRESH mid-takeover (the ABA race: reader sees the stale timestamp, a winner
59
- // re-creates the lock, the reader deletes the winner's file). Under the guard the staleness is
60
- // re-checked and only a genuinely stale lock is cleared; losers simply refuse. A guard left by a
61
- // crashed taker expires like the lock itself. Returns whether to re-race the create.
62
- const takeOverStale = (now) => {
63
- const guard = `${lockPath()}.takeover`;
64
- const guardHeld = holderOf(guard);
65
- if (guardHeld !== null && isCrashed(guardHeld, now))
66
- unlinkQuiet(guard);
67
- try {
68
- writeFileSync(guard, `${process.pid} ${now}`, { flag: 'wx' });
69
- }
70
- catch {
71
- return false; // another taker is mid-takeover; let it win
72
- }
73
- try {
74
- const holder = holderOf(lockPath());
75
- if (holder !== null && !isCrashed(holder, now))
76
- return false; // fresh or a live-but-slow holder
77
- unlinkQuiet(lockPath());
78
- return true;
79
- }
80
- finally {
81
- unlinkQuiet(guard);
82
- }
83
- };
84
- // Acquire the single-writer update lock. The O_EXCL ('wx') create IS the mutex - concurrent runs
85
- // cannot both win it, unlike a check-then-write. A fresh holder (younger than the TTL) refuses;
86
- // a stale one (crashed run) is cleared under the takeover guard and re-raced once. `now` is
87
- // injectable.
88
- export const acquireUpdateLock = (now = Date.now()) => {
89
- ensureConfigDir();
90
- for (let attempt = 0; attempt < 2; attempt++) {
91
- try {
92
- writeFileSync(lockPath(), `${process.pid} ${now}`, { flag: 'wx' });
93
- return true;
94
- }
95
- catch {
96
- const holder = holderOf(lockPath());
97
- if (holder !== null && !isCrashed(holder, now))
98
- return false;
99
- if (!takeOverStale(now))
100
- return false;
101
- }
102
- }
103
- return false;
104
- };
105
- export const releaseUpdateLock = () => {
106
- const path = lockPath();
107
- if (existsSync(path))
108
- unlinkSync(path);
109
- };