@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.
- package/dist/cli.js +5 -3
- package/dist/commands/auth/setup.d.ts +6 -14
- package/dist/commands/auth/setup.js +9 -121
- package/dist/commands/plugins/status-print.d.ts +2 -0
- package/dist/commands/plugins/status-print.js +44 -0
- package/dist/commands/plugins/status-rows.d.ts +51 -0
- package/dist/commands/plugins/status-rows.js +58 -0
- package/dist/commands/status/status.d.ts +9 -3
- package/dist/commands/status/status.js +53 -8
- package/dist/commands/update/update.d.ts +3 -1
- package/dist/commands/update/update.js +25 -22
- package/dist/core/loader/discover.d.ts +33 -0
- package/dist/core/loader/discover.js +103 -0
- package/dist/core/loader/manifest.d.ts +16 -0
- package/dist/core/loader/manifest.js +66 -0
- package/dist/core/loader/state.d.ts +12 -0
- package/dist/core/loader/state.js +21 -0
- package/dist/core/loader/states.d.ts +7 -0
- package/dist/core/loader/states.js +52 -0
- package/dist/core/paths.d.ts +4 -0
- package/dist/core/paths.js +6 -0
- package/dist/core/profile/machine.d.ts +18 -0
- package/dist/core/profile/machine.js +60 -0
- package/dist/core/profile/merge.d.ts +11 -0
- package/dist/core/profile/merge.js +16 -0
- package/dist/core/profile/profile.d.ts +6 -0
- package/dist/core/profile/profile.js +34 -0
- package/dist/core/profile/schema.d.ts +26 -0
- package/dist/core/profile/schema.js +143 -0
- package/dist/core/semver-range.d.ts +16 -0
- package/dist/core/semver-range.js +116 -0
- package/dist/lib/auth/api.d.ts +3 -2
- package/dist/lib/auth/api.js +50 -56
- package/dist/lib/auth/callback-page.d.ts +2 -0
- package/dist/lib/auth/callback-page.js +40 -0
- package/dist/lib/auth/callback-server.js +1 -4
- package/dist/lib/auth/env-match.js +1 -0
- package/dist/lib/auth/introspect.js +10 -11
- package/dist/lib/auth/oauth.d.ts +2 -0
- package/dist/lib/auth/oauth.js +30 -28
- package/dist/lib/auth/sign-in.d.ts +20 -0
- package/dist/lib/auth/sign-in.js +95 -0
- package/dist/lib/fs/config.d.ts +1 -0
- package/dist/lib/fs/config.js +14 -21
- package/dist/lib/fs/file-lock.d.ts +7 -2
- package/dist/lib/fs/file-lock.js +64 -74
- package/dist/lib/fs/json-file.d.ts +3 -0
- package/dist/lib/fs/json-file.js +43 -0
- package/dist/lib/net/fetch.d.ts +10 -0
- package/dist/lib/net/fetch.js +13 -0
- package/dist/lib/net/http.js +51 -23
- package/dist/lib/npm/npm-cli.d.ts +17 -0
- package/dist/lib/npm/npm-cli.js +77 -0
- package/dist/lib/status/status-info.js +1 -0
- package/dist/lib/update/update-cache.d.ts +1 -0
- package/dist/lib/update/update-cache.js +7 -11
- package/dist/lib/update/update-check.js +1 -4
- package/dist/program.d.ts +18 -1
- package/dist/program.js +75 -32
- package/package.json +1 -1
- package/dist/lib/update/update-lock.d.ts +0 -2
- 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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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,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
|
-
};
|