@agentage/cli 0.39.0 → 0.41.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 (145) hide show
  1. package/README.md +126 -25
  2. package/dist/commands/auth/setup.d.ts +8 -2
  3. package/dist/commands/auth/setup.js +38 -7
  4. package/dist/commands/extension/account.d.ts +5 -0
  5. package/dist/commands/extension/account.js +18 -0
  6. package/dist/commands/extension/build.d.ts +10 -0
  7. package/dist/commands/extension/build.js +27 -0
  8. package/dist/commands/extension/delegate.d.ts +6 -0
  9. package/dist/commands/extension/delegate.js +57 -0
  10. package/dist/commands/extension/doctor.d.ts +13 -0
  11. package/dist/commands/extension/doctor.js +44 -0
  12. package/dist/commands/extension/install-load.d.ts +7 -0
  13. package/dist/commands/extension/install-load.js +42 -0
  14. package/dist/commands/extension/install.d.ts +10 -0
  15. package/dist/commands/extension/install.js +38 -0
  16. package/dist/commands/extension/kit-commands.d.ts +13 -0
  17. package/dist/commands/extension/kit-commands.js +42 -0
  18. package/dist/commands/extension/kit-tool.d.ts +31 -0
  19. package/dist/commands/extension/kit-tool.js +5 -0
  20. package/dist/commands/extension/latest.d.ts +2 -0
  21. package/dist/commands/extension/latest.js +71 -0
  22. package/dist/commands/extension/list.d.ts +22 -0
  23. package/dist/commands/extension/list.js +116 -0
  24. package/dist/commands/extension/plan.d.ts +8 -0
  25. package/dist/commands/extension/plan.js +67 -0
  26. package/dist/commands/extension/remove.d.ts +15 -0
  27. package/dist/commands/extension/remove.js +86 -0
  28. package/dist/commands/extension/row-lines.d.ts +4 -0
  29. package/dist/commands/extension/row-lines.js +68 -0
  30. package/dist/commands/extension/rows.d.ts +58 -0
  31. package/dist/commands/extension/rows.js +67 -0
  32. package/dist/commands/extension/secrets-prompt.d.ts +6 -0
  33. package/dist/commands/extension/secrets-prompt.js +47 -0
  34. package/dist/commands/extension/setup-cloud.d.ts +6 -0
  35. package/dist/commands/extension/setup-cloud.js +98 -0
  36. package/dist/commands/extension/setup.d.ts +5 -0
  37. package/dist/commands/extension/setup.js +68 -0
  38. package/dist/commands/extension/sync.d.ts +22 -0
  39. package/dist/commands/extension/sync.js +81 -0
  40. package/dist/commands/extension/up.d.ts +22 -0
  41. package/dist/commands/extension/up.js +76 -0
  42. package/dist/commands/status/account-status.d.ts +3 -0
  43. package/dist/commands/status/account-status.js +27 -0
  44. package/dist/commands/status/status.d.ts +3 -2
  45. package/dist/commands/status/status.js +36 -36
  46. package/dist/core/installer/compare-lock.d.ts +8 -0
  47. package/dist/core/installer/compare-lock.js +51 -0
  48. package/dist/core/installer/compat.d.ts +20 -0
  49. package/dist/core/installer/compat.js +44 -0
  50. package/dist/core/installer/installer.d.ts +28 -0
  51. package/dist/core/installer/installer.js +87 -0
  52. package/dist/core/installer/latest.d.ts +27 -0
  53. package/dist/core/installer/latest.js +90 -0
  54. package/dist/core/installer/npm.d.ts +11 -0
  55. package/dist/core/installer/npm.js +76 -0
  56. package/dist/core/installer/plan.d.ts +25 -0
  57. package/dist/core/installer/plan.js +52 -0
  58. package/dist/core/installer/resolve.d.ts +26 -0
  59. package/dist/core/installer/resolve.js +140 -0
  60. package/dist/core/installer/tree.d.ts +21 -0
  61. package/dist/core/installer/tree.js +100 -0
  62. package/dist/core/kit-version.d.ts +1 -0
  63. package/dist/core/kit-version.js +15 -0
  64. package/dist/core/loader/config.d.ts +7 -0
  65. package/dist/core/loader/config.js +59 -0
  66. package/dist/core/loader/ctx.d.ts +16 -0
  67. package/dist/core/loader/ctx.js +76 -0
  68. package/dist/core/loader/dev-path.d.ts +1 -0
  69. package/dist/core/loader/dev-path.js +20 -0
  70. package/dist/core/loader/discover.d.ts +17 -11
  71. package/dist/core/loader/discover.js +50 -31
  72. package/dist/core/loader/dispose.d.ts +4 -0
  73. package/dist/core/loader/dispose.js +24 -0
  74. package/dist/core/loader/kit-manifest.d.ts +9 -0
  75. package/dist/core/loader/kit-manifest.js +52 -0
  76. package/dist/core/loader/load.d.ts +14 -0
  77. package/dist/core/loader/load.js +78 -0
  78. package/dist/core/loader/log.d.ts +3 -0
  79. package/dist/core/loader/log.js +23 -0
  80. package/dist/core/loader/manifest.d.ts +8 -4
  81. package/dist/core/loader/manifest.js +54 -12
  82. package/dist/core/loader/order.d.ts +2 -0
  83. package/dist/core/loader/order.js +20 -0
  84. package/dist/core/loader/register.d.ts +19 -0
  85. package/dist/core/loader/register.js +39 -0
  86. package/dist/core/loader/scan.d.ts +18 -0
  87. package/dist/core/loader/scan.js +34 -0
  88. package/dist/core/loader/secrets.d.ts +9 -0
  89. package/dist/core/loader/secrets.js +28 -0
  90. package/dist/core/loader/state.d.ts +4 -4
  91. package/dist/core/loader/state.js +17 -7
  92. package/dist/core/loader/states.d.ts +2 -2
  93. package/dist/core/loader/states.js +5 -5
  94. package/dist/core/loader/timeout.d.ts +1 -0
  95. package/dist/core/loader/timeout.js +8 -0
  96. package/dist/core/loader/use.d.ts +8 -0
  97. package/dist/core/loader/use.js +18 -0
  98. package/dist/core/loader/verbs.d.ts +15 -0
  99. package/dist/core/loader/verbs.js +57 -0
  100. package/dist/core/paths.d.ts +6 -1
  101. package/dist/core/paths.js +6 -1
  102. package/dist/core/profile/cloud.d.ts +53 -0
  103. package/dist/core/profile/cloud.js +109 -0
  104. package/dist/core/profile/edit.d.ts +4 -0
  105. package/dist/core/profile/edit.js +27 -0
  106. package/dist/core/profile/machine.d.ts +11 -11
  107. package/dist/core/profile/machine.js +66 -43
  108. package/dist/core/profile/merge.d.ts +1 -1
  109. package/dist/core/profile/merge.js +7 -2
  110. package/dist/core/profile/profile.d.ts +6 -6
  111. package/dist/core/profile/profile.js +50 -16
  112. package/dist/core/profile/push.d.ts +16 -0
  113. package/dist/core/profile/push.js +49 -0
  114. package/dist/core/profile/register.d.ts +5 -0
  115. package/dist/core/profile/register.js +57 -0
  116. package/dist/core/profile/schema.d.ts +3 -1
  117. package/dist/core/profile/schema.js +39 -17
  118. package/dist/core/semver-range.d.ts +1 -0
  119. package/dist/core/semver-range.js +8 -0
  120. package/dist/extension-test.d.ts +7 -0
  121. package/dist/extension-test.js +49 -0
  122. package/dist/extension.d.ts +83 -0
  123. package/dist/extension.js +6 -0
  124. package/dist/lib/auth/api.d.ts +2 -2
  125. package/dist/lib/auth/api.js +3 -3
  126. package/dist/lib/fs/file-lock.d.ts +3 -1
  127. package/dist/lib/fs/file-lock.js +26 -11
  128. package/dist/lib/fs/json-file.d.ts +1 -0
  129. package/dist/lib/fs/json-file.js +3 -2
  130. package/dist/lib/net/fetch.js +1 -1
  131. package/dist/lib/net/origins.d.ts +1 -0
  132. package/dist/lib/net/origins.js +11 -11
  133. package/dist/lib/npm/npm-cli.d.ts +2 -0
  134. package/dist/lib/npm/npm-cli.js +4 -4
  135. package/dist/program.d.ts +17 -1
  136. package/dist/program.js +61 -11
  137. package/package.json +9 -4
  138. package/dist/commands/plugins/status-print.d.ts +0 -2
  139. package/dist/commands/plugins/status-print.js +0 -44
  140. package/dist/commands/plugins/status-rows.d.ts +0 -51
  141. package/dist/commands/plugins/status-rows.js +0 -58
  142. package/dist/core/flag.d.ts +0 -1
  143. package/dist/core/flag.js +0 -1
  144. package/dist/plugin.d.ts +0 -1
  145. package/dist/plugin.js +0 -2
@@ -0,0 +1,47 @@
1
+ import { secretsFor } from '../../core/loader/secrets.js';
2
+ const ENTER = ['\r', '\n', '\u0004'];
3
+ const CTRL_C = '\u0003';
4
+ const BACKSPACE = '\u007f';
5
+ // One keypress: the answer so far, or null once Enter ends it.
6
+ const typed = (answer, char) => {
7
+ if (ENTER.includes(char))
8
+ return null;
9
+ return char === BACKSPACE ? answer.slice(0, -1) : answer + char;
10
+ };
11
+ // Raw mode keeps the answer off the screen; Ctrl+C still interrupts once the terminal is restored.
12
+ const readHidden = (question) => new Promise((resolve) => {
13
+ const { stdin, stderr } = process;
14
+ let answer = '';
15
+ const onData = (chunk) => {
16
+ for (const char of chunk.toString('utf-8')) {
17
+ const next = char === CTRL_C ? null : typed(answer, char);
18
+ if (next === null)
19
+ return finish(char === CTRL_C);
20
+ answer = next;
21
+ }
22
+ };
23
+ const finish = (interrupted) => {
24
+ stdin.off('data', onData).setRawMode(false).pause();
25
+ stderr.write('\n');
26
+ resolve(answer);
27
+ if (interrupted)
28
+ process.kill(process.pid, 'SIGINT');
29
+ };
30
+ stderr.write(question);
31
+ stdin.setRawMode(true).resume().on('data', onData);
32
+ });
33
+ const terminal = () => ({ isTTY: process.stdin.isTTY === true, prompt: readHidden });
34
+ const nameOf = ({ id, key }) => `${id}.${key}`;
35
+ // Before loading: a terminal is asked for each missing secret, anything else gets the list.
36
+ export const askSecrets = async (missing, io = terminal()) => {
37
+ if (missing.length === 0)
38
+ return;
39
+ if (!io.isTTY)
40
+ return void console.log(`secrets to set: ${missing.map(nameOf).join(', ')}`);
41
+ for (const secret of missing) {
42
+ const described = secret.description === null ? '' : ` (${secret.description})`;
43
+ const value = await io.prompt(`${nameOf(secret)}${described}: `);
44
+ if (value !== '')
45
+ await secretsFor(secret.id).set(secret.key, value);
46
+ }
47
+ };
@@ -0,0 +1,6 @@
1
+ import { type EditDeps } from './sync.js';
2
+ export interface AccountSetupDeps extends EditDeps {
3
+ signOut: () => Promise<void>;
4
+ }
5
+ export declare const accountSetup: (deps: EditDeps) => Promise<void>;
6
+ export declare const disconnect: (deps: AccountSetupDeps) => Promise<void>;
@@ -0,0 +1,98 @@
1
+ import chalk from 'chalk';
2
+ import { localProfile, NotPushedError, pullProfile, } from '../../core/profile/cloud.js';
3
+ import { forgetAccount, loadMachine, readMachine, setAccount, setCloud, } from '../../core/profile/machine.js';
4
+ import { defaultProfile, mutateProfile, readProfile } from '../../core/profile/profile.js';
5
+ import { pushWithReplay } from '../../core/profile/push.js';
6
+ import { disconnectMachine, registerMachine } from '../../core/profile/register.js';
7
+ import {} from '../../core/profile/schema.js';
8
+ import { installAndLoad, line } from './install-load.js';
9
+ import { applying, checkAccount } from './sync.js';
10
+ const FILE_NOTE = 'from a file (setup --profile), not synced with your account';
11
+ // profile.json: null when absent; an invalid one stops setup naming each problem.
12
+ const localCopy = () => {
13
+ const parsed = readProfile();
14
+ if (parsed === null)
15
+ return null;
16
+ if (parsed.ok)
17
+ return parsed.profile;
18
+ throw new Error(`profile.json is invalid:\n${parsed.errors.map((e) => ` ${e}`).join('\n')}`);
19
+ };
20
+ // The etag profile.json still mirrors: sent as If-None-Match, so an unchanged profile is a 304.
21
+ const mirroredEtag = (machine) => machine.profileSource === 'cloud' && readProfile()?.ok === true ? machine.profileEtag : null;
22
+ // Without the account: the local profile.json; with none, nothing to install, and setup goes on.
23
+ const fromLocalCopy = async (reason, deps) => {
24
+ const local = localCopy();
25
+ const unavailable = 'cloud profile unavailable';
26
+ if (local === null)
27
+ return console.error(chalk.dim(`${unavailable} and no local profile.json, nothing installed (${reason})`));
28
+ console.error(chalk.dim(`${unavailable}, using local profile.json (${reason})`));
29
+ await installAndLoad(local, deps.npm);
30
+ };
31
+ const installPulled = async (doc, deps) => {
32
+ await mutateProfile(() => doc.profile);
33
+ console.log(line('profile', `pulled from your account (etag ${doc.etag})`));
34
+ await installAndLoad({ ...doc.profile, lock: doc.lock }, deps.npm, () => setCloud(doc.etag));
35
+ };
36
+ // A create the account did not take fails nothing: the next setup creates it.
37
+ const notCreated = (err) => {
38
+ if (!(err instanceof NotPushedError))
39
+ throw err;
40
+ console.error(chalk.dim(`${err.message}; the next agentage setup tries again`));
41
+ return null;
42
+ };
43
+ // No profile in the account yet: this machine's own, or the default, becomes it once it loaded.
44
+ const createIn = async (account, deps) => {
45
+ const base = localProfile(localCopy() ?? defaultProfile());
46
+ const apply = applying((profile) => profile, deps);
47
+ const done = await pushWithReplay(account.fetch, base, apply).catch(notCreated);
48
+ if (done === null)
49
+ return;
50
+ await setCloud(done.etag);
51
+ const where = done.pushed ? 'created in' : 'already in';
52
+ console.log(line('profile', `${where} your account (etag ${done.etag})`));
53
+ };
54
+ // Plain setup pulls and never pushes, except to create the account's first profile.
55
+ const profileFrom = async (account, machine, deps) => {
56
+ if (machine.profileSource === 'file') {
57
+ console.log(line('profile', FILE_NOTE));
58
+ return installAndLoad(localCopy() ?? defaultProfile(), deps.npm);
59
+ }
60
+ const pulled = await pullProfile(account.fetch, mirroredEtag(machine));
61
+ if (pulled.kind === 'found')
62
+ return installPulled(pulled.doc, deps);
63
+ if (pulled.kind === 'none')
64
+ return createIn(account, deps);
65
+ if (pulled.kind === 'current')
66
+ return installAndLoad(localCopy() ?? defaultProfile(), deps.npm);
67
+ return fromLocalCopy(pulled.reason, deps);
68
+ };
69
+ // The first signed-in setup records the account; another account is refused until --disconnect.
70
+ const connect = async (account) => {
71
+ const machine = await loadMachine();
72
+ const userId = await checkAccount(account, machine);
73
+ return userId === null || machine.accountId !== null ? machine : setAccount(userId);
74
+ };
75
+ // Registration runs alongside the profile, so it never delays setup by more than its 2 s.
76
+ export const accountSetup = async (deps) => {
77
+ const account = deps.account();
78
+ if (account === null)
79
+ return fromLocalCopy('not signed in', deps);
80
+ const machine = await connect(account);
81
+ const registered = registerMachine(account.fetch, machine);
82
+ try {
83
+ await profileFrom(account, machine, deps);
84
+ }
85
+ finally {
86
+ await registered;
87
+ }
88
+ };
89
+ // setup --disconnect: the account marks this machine disconnected, then it forgets the account.
90
+ export const disconnect = async (deps) => {
91
+ const machine = readMachine();
92
+ const account = deps.account();
93
+ if (machine !== null && account !== null)
94
+ await disconnectMachine(account.fetch, machine.id);
95
+ await deps.signOut();
96
+ if (machine !== null)
97
+ await forgetAccount();
98
+ };
@@ -0,0 +1,5 @@
1
+ import { type SetupDeps, type SetupOptions } from '../auth/setup.js';
2
+ import { type EditDeps } from './sync.js';
3
+ export interface ExtensionSetupDeps extends SetupDeps, EditDeps {
4
+ }
5
+ export declare const runExtensionSetup: (opts: SetupOptions, deps: ExtensionSetupDeps) => Promise<void>;
@@ -0,0 +1,68 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { loadMachine, setProfileSource } from '../../core/profile/machine.js';
3
+ import { keepMachine } from '../../core/profile/merge.js';
4
+ import { defaultProfile, mutateProfile, parseProfileText, readProfile, } from '../../core/profile/profile.js';
5
+ import {} from '../../core/profile/schema.js';
6
+ import {} from '../auth/setup.js';
7
+ import { installAndLoad } from './install-load.js';
8
+ import { runSetupLatest } from './latest.js';
9
+ import { printDryRun, runSetupCheck } from './plan.js';
10
+ import { printWarnings } from './row-lines.js';
11
+ import { accountSetup, disconnect } from './setup-cloud.js';
12
+ import {} from './sync.js';
13
+ const sourceOf = (label, parsed) => {
14
+ if (!parsed.ok)
15
+ throw new Error(`${label} is invalid:\n${parsed.errors.map((e) => ` ${e}`).join('\n')}`);
16
+ return { profile: parsed.profile, warnings: parsed.warnings };
17
+ };
18
+ const fileSource = (file) => {
19
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(file))
20
+ throw new Error(`${file}: profile URLs are not supported; pass a file path`);
21
+ let raw;
22
+ try {
23
+ raw = readFileSync(file, 'utf-8');
24
+ }
25
+ catch (err) {
26
+ throw new Error(`cannot read profile ${file}: ${err.message}`);
27
+ }
28
+ return sourceOf(file, parseProfileText(raw));
29
+ };
30
+ // For --check and --dry-run: the local profile.json, else the built-in default.
31
+ const localSource = () => {
32
+ const parsed = readProfile();
33
+ if (parsed !== null)
34
+ return sourceOf('profile.json', parsed);
35
+ return { profile: defaultProfile(), warnings: [] };
36
+ };
37
+ // --check and --dry-run read only and never sign in.
38
+ const readOnly = (source, opts) => {
39
+ printWarnings(source.warnings);
40
+ if (opts.check)
41
+ runSetupCheck(source.profile, opts);
42
+ else
43
+ printDryRun(source.profile, opts);
44
+ };
45
+ // `--profile` never signs in nor calls the account; this machine's own entry is kept.
46
+ const fromFile = async (source, deps) => {
47
+ const machine = await loadMachine();
48
+ await mutateProfile((current) => keepMachine(source.profile, current, machine.id));
49
+ await setProfileSource('file');
50
+ await installAndLoad(source.profile, deps.npm);
51
+ };
52
+ export const runExtensionSetup = async (opts, deps) => {
53
+ if (opts.disconnect)
54
+ return disconnect(deps);
55
+ const file = opts.profile === undefined ? null : fileSource(opts.profile);
56
+ if (opts.check || opts.dryRun)
57
+ return readOnly(file ?? localSource(), opts);
58
+ if (file)
59
+ return fromFile(file, deps);
60
+ if (opts.latest)
61
+ return runSetupLatest(deps);
62
+ await deps.ensureSignedIn({ reauth: opts.reauth, browser: opts.browser });
63
+ const token = process.env['AGENTAGE_TOKEN'];
64
+ const failure = await accountSetup(deps).then(() => null, (error) => error);
65
+ await deps.printStatus({ token });
66
+ if (failure)
67
+ throw failure;
68
+ };
@@ -0,0 +1,22 @@
1
+ import { type Npm } from '../../core/installer/npm.js';
2
+ import { type Account, type CloudFetch, type CloudProfile } from '../../core/profile/cloud.js';
3
+ import { type MachineFile } from '../../core/profile/machine.js';
4
+ import { type Apply, type LockPair, type Pushed, type ReplayOptions } from '../../core/profile/push.js';
5
+ import { type Profile } from '../../core/profile/schema.js';
6
+ import { type AccountOf } from './account.js';
7
+ export interface EditDeps {
8
+ npm: () => Promise<Npm>;
9
+ npmMajor: () => Promise<number>;
10
+ account: AccountOf;
11
+ }
12
+ export type Change = (profile: Profile) => Profile;
13
+ export interface Synced {
14
+ fetch: CloudFetch;
15
+ base: CloudProfile;
16
+ }
17
+ export declare const checkAccount: (account: Account, machine: MachineFile) => Promise<string | null>;
18
+ export declare const pullForEdit: (deps: EditDeps) => Promise<Synced | null>;
19
+ export declare const localLock: (deps: EditDeps) => Promise<LockPair>;
20
+ export declare const applying: (change: Change, deps: EditDeps, note?: string | null) => Apply;
21
+ export declare const pushApplied: (synced: Synced, apply: Apply, opts?: ReplayOptions) => Promise<Pushed>;
22
+ export declare const finishEdit: (synced: Synced | null, change: Change, deps: EditDeps, note?: string | null) => Promise<void>;
@@ -0,0 +1,81 @@
1
+ import { join } from 'node:path';
2
+ import { parseLock } from '../../core/installer/compare-lock.js';
3
+ import {} from '../../core/installer/npm.js';
4
+ import { LOCK_FILE, readText } from '../../core/installer/tree.js';
5
+ import { extensionsDir } from '../../core/paths.js';
6
+ import { localProfile, NotPushedError, pullProfile, } from '../../core/profile/cloud.js';
7
+ import { changeProfile } from '../../core/profile/edit.js';
8
+ import { accountMismatch, loadMachine, setCloud, } from '../../core/profile/machine.js';
9
+ import { editableProfile, mutateProfile } from '../../core/profile/profile.js';
10
+ import { pushBody, pushWithReplay, } from '../../core/profile/push.js';
11
+ import {} from '../../core/profile/schema.js';
12
+ import {} from './account.js';
13
+ import { installAndLoad, line } from './install-load.js';
14
+ const NOT_SIGNED_IN = 'not signed in - run: agentage setup (this machine syncs its profile with your account)';
15
+ const REPLAY = 'changed in your account meanwhile, replaying this change on it';
16
+ // The signed-in account's id, refused when this machine is connected to another account.
17
+ export const checkAccount = async (account, machine) => {
18
+ const userId = await account.id();
19
+ const mismatch = accountMismatch(machine, userId);
20
+ if (mismatch)
21
+ throw mismatch;
22
+ return userId;
23
+ };
24
+ // Pulled whole, before anything changes, so the edit applies to the account's newest document.
25
+ export const pullForEdit = async (deps) => {
26
+ const machine = await loadMachine();
27
+ if (machine.profileSource !== 'cloud')
28
+ return null;
29
+ const account = deps.account();
30
+ if (account === null)
31
+ throw new Error(NOT_SIGNED_IN);
32
+ await checkAccount(account, machine);
33
+ const pulled = await pullProfile(account.fetch, null);
34
+ if (pulled.kind === 'unavailable')
35
+ throw new Error(`cloud profile unavailable (${pulled.reason}); nothing changed`);
36
+ const base = pulled.kind === 'found' ? pulled.doc : localProfile(editableProfile());
37
+ return { fetch: account.fetch, base };
38
+ };
39
+ // extensions/package-lock.json and the npm major that wrote it, pushed with the profile.
40
+ export const localLock = async (deps) => {
41
+ const lock = parseLock(readText(join(extensionsDir(), LOCK_FILE)));
42
+ return { lock, npmMajor: lock === null ? null : await deps.npmMajor() };
43
+ };
44
+ // Nothing is pushed unless the new tree installed and loaded: installAndLoad throws otherwise.
45
+ export const applying = (change, deps, note = null) => {
46
+ let calls = 0;
47
+ return async (base) => {
48
+ const next = change(base.profile);
49
+ await mutateProfile(() => next);
50
+ const text = calls++ === 0 ? note : line('profile', REPLAY);
51
+ if (text !== null)
52
+ console.log(text);
53
+ await installAndLoad({ ...next, lock: base.lock }, deps.npm);
54
+ return pushBody(next, base, await localLock(deps));
55
+ };
56
+ };
57
+ const onThisMachine = async (change, deps, note) => {
58
+ await changeProfile(change);
59
+ if (note !== null)
60
+ console.log(note);
61
+ await installAndLoad(editableProfile(), deps.npm);
62
+ };
63
+ const installedHere = (err) => {
64
+ if (!(err instanceof NotPushedError))
65
+ throw err;
66
+ throw new Error(`${err.message}; installed here only, run the command again to push it`);
67
+ };
68
+ // `apply` installs and loads before it returns the body: a failed load pushes nothing.
69
+ export const pushApplied = async (synced, apply, opts = {}) => {
70
+ const done = await pushWithReplay(synced.fetch, synced.base, apply, opts).catch(installedHere);
71
+ await setCloud(done.etag);
72
+ return done;
73
+ };
74
+ // install, remove and --off end here; the account gets the change only after the tree loaded.
75
+ export const finishEdit = async (synced, change, deps, note = null) => {
76
+ if (synced === null)
77
+ return onThisMachine(change, deps, note);
78
+ const done = await pushApplied(synced, applying(change, deps, note));
79
+ const where = done.pushed ? 'pushed to' : 'already in';
80
+ console.log(line('profile', `${where} your account (etag ${done.etag})`));
81
+ };
@@ -0,0 +1,22 @@
1
+ import { type Command } from 'commander';
2
+ import { type LoadKitTool } from './kit-tool.js';
3
+ export interface ExtensionUpOptions {
4
+ dir?: string;
5
+ }
6
+ export interface ExtensionDownOptions extends ExtensionUpOptions {
7
+ keepData?: boolean;
8
+ }
9
+ export interface ExtensionTokenOptions {
10
+ scope: string;
11
+ issuer?: string;
12
+ }
13
+ export declare const runExtensionUp: (opts?: ExtensionUpOptions, tool?: LoadKitTool) => Promise<void>;
14
+ export declare const runExtensionDown: (opts?: ExtensionDownOptions, tool?: LoadKitTool) => Promise<void>;
15
+ export declare const runExtensionToken: (opts: ExtensionTokenOptions, tool?: LoadKitTool) => Promise<void>;
16
+ export declare const issuerUrl: (value: string) => string;
17
+ export interface KitActions {
18
+ up: (opts: ExtensionUpOptions) => Promise<void>;
19
+ down: (opts: ExtensionDownOptions) => Promise<void>;
20
+ token: (opts: ExtensionTokenOptions) => Promise<void>;
21
+ }
22
+ export declare const addKitTools: (group: Command, actions: KitActions) => void;
@@ -0,0 +1,76 @@
1
+ import { InvalidArgumentError } from 'commander';
2
+ import { loadKitTool } from './kit-tool.js';
3
+ const print = (line) => console.log(line);
4
+ // Each step's line as the kit reports it, the ready line last.
5
+ export const runExtensionUp = async (opts = {}, tool = loadKitTool) => {
6
+ const { up } = await tool();
7
+ await up({ ...opts, onLine: print });
8
+ };
9
+ export const runExtensionDown = async (opts = {}, tool = loadKitTool) => {
10
+ const { down } = await tool();
11
+ await down({ ...opts, onLine: print });
12
+ };
13
+ // The token alone on stdout, so a script can take it as it is.
14
+ export const runExtensionToken = async (opts, tool = loadKitTool) => {
15
+ const { token } = await tool();
16
+ console.log(await token(opts));
17
+ };
18
+ // A usage error (exit 2) before the kit is asked: the issuer is an http: or https: URL.
19
+ export const issuerUrl = (value) => {
20
+ let protocol;
21
+ try {
22
+ protocol = new URL(value).protocol;
23
+ }
24
+ catch {
25
+ throw new InvalidArgumentError('not a URL.');
26
+ }
27
+ if (protocol === 'http:' || protocol === 'https:')
28
+ return value;
29
+ throw new InvalidArgumentError('not an http: or https: URL.');
30
+ };
31
+ const UP_HELP = `
32
+ Runs the extension package in --dir (default: the current folder) on this machine with Docker
33
+ only, the way a deployment runs it: the part images built with the release recipe, the store
34
+ with its migrations and seeds, a dev token issuer and a local gateway. Prints one line per step
35
+ as the kit reports it, the last one once every part is healthy. Needs docker. Exits 1 when a
36
+ step fails, with the kit's message.`;
37
+ const DOWN_HELP = `
38
+ Removes what agentage extension up started for the package in --dir: its containers, network
39
+ and the store's volume; --keep-data keeps the volume. Exits 1 when that fails.`;
40
+ const TOKEN_HELP = `
41
+ Prints a token from the dev issuer agentage extension up started, alone on stdout, for calling
42
+ /api/v1/ and /mcp by hand or from tests. Only up trusts it. Exits 1 when the issuer refuses.`;
43
+ const DIR = '--dir <path>';
44
+ const DIR_TEXT = 'the extension package folder (default: the current folder)';
45
+ const addUp = (group, up) => {
46
+ group
47
+ .command('up')
48
+ .description('Run the extension package on this machine: images, store, issuer, gateway')
49
+ .option(DIR, DIR_TEXT)
50
+ .addHelpText('after', UP_HELP)
51
+ .action(up);
52
+ };
53
+ const addDown = (group, down) => {
54
+ group
55
+ .command('down')
56
+ .description('Remove what extension up started; --keep-data keeps the store')
57
+ .option(DIR, DIR_TEXT)
58
+ .option('--keep-data', "keep the store's volume")
59
+ .addHelpText('after', DOWN_HELP)
60
+ .action(down);
61
+ };
62
+ const addToken = (group, token) => {
63
+ group
64
+ .command('token')
65
+ .description('Print a token from the dev issuer extension up started')
66
+ .requiredOption('--scope <scope>', 'the scope the token carries, e.g. probe:write')
67
+ .option('--issuer <url>', 'the dev issuer (default: http://localhost:4281)', issuerUrl)
68
+ .addHelpText('after', TOKEN_HELP)
69
+ .action(token);
70
+ };
71
+ // up, down and token: thin commands over the kit's tool library, for extension authors.
72
+ export const addKitTools = (group, actions) => {
73
+ addUp(group, actions.up);
74
+ addDown(group, actions.down);
75
+ addToken(group, actions.token);
76
+ };
@@ -0,0 +1,3 @@
1
+ import { type Account } from '../../core/profile/cloud.js';
2
+ import { type ExtensionsGather } from '../extension/rows.js';
3
+ export declare const gatherWithAccount: (account: Account | null) => Promise<ExtensionsGather>;
@@ -0,0 +1,27 @@
1
+ import { pullProfile } from '../../core/profile/cloud.js';
2
+ import { seenMachine } from '../../core/profile/register.js';
3
+ import { gatherExtensions } from '../extension/rows.js';
4
+ const STATUS_TIMEOUT_MS = 2_000;
5
+ // The account's profile when it moved past the local copy; null when the same or not known.
6
+ const accountProfile = async (fetch, profile) => {
7
+ if (profile.state !== 'ok' || profile.etag === null)
8
+ return null;
9
+ const pulled = await pullProfile(fetch, profile.etag, STATUS_TIMEOUT_MS);
10
+ if (pulled.kind !== 'found')
11
+ return null;
12
+ return { version: pulled.doc.profile.version, etag: pulled.doc.etag };
13
+ };
14
+ // `status`: marks this machine seen (does the account know it?) and compares the profile copies.
15
+ export const gatherWithAccount = async (account) => {
16
+ const gathered = await gatherExtensions();
17
+ const { machine, profile } = gathered.status;
18
+ const [registered, cloud] = account === null
19
+ ? [null, null]
20
+ : await Promise.all([
21
+ seenMachine(account.fetch, machine.id),
22
+ accountProfile(account.fetch, profile),
23
+ ]);
24
+ const compared = cloud !== null && profile.state === 'ok' ? { ...profile, cloud } : profile;
25
+ const status = { ...gathered.status, machine: { ...machine, registered }, profile: compared };
26
+ return { ...gathered, status };
27
+ };
@@ -1,6 +1,6 @@
1
1
  import { type Command } from 'commander';
2
2
  import { type StatusReport } from '../../lib/status/status-info.js';
3
- import { type PluginsGather } from '../plugins/status-rows.js';
3
+ import { type ExtensionsGather } from '../extension/rows.js';
4
4
  export declare const printStatus: (report: StatusReport) => void;
5
5
  export interface StatusOptions {
6
6
  json?: boolean;
@@ -8,7 +8,8 @@ export interface StatusOptions {
8
8
  }
9
9
  export interface StatusDeps {
10
10
  gather: (opts: StatusOptions) => Promise<StatusReport>;
11
- plugins: () => Promise<PluginsGather | null>;
11
+ extensions: (opts: StatusOptions) => Promise<ExtensionsGather>;
12
12
  }
13
+ export declare const statusDeps: (account?: boolean) => StatusDeps;
13
14
  export declare const runStatus: (opts?: StatusOptions, deps?: StatusDeps) => Promise<void>;
14
15
  export declare const registerStatus: (program: Command, action: (opts: StatusOptions) => Promise<void>) => void;
@@ -1,12 +1,13 @@
1
1
  import chalk from 'chalk';
2
2
  import {} from 'commander';
3
- import { pluginsEnabled } from '../../core/flag.js';
4
3
  import { resolveAuth } from '../../lib/auth/credentials.js';
5
4
  import { siteFqdn } from '../../lib/net/origins.js';
6
5
  import { gatherStatus } from '../../lib/status/status-info.js';
7
6
  import { INSTALL_HINT } from '../../lib/update/update-check.js';
8
- import { pluginStatusLines } from '../plugins/status-print.js';
9
- import { gatherPluginStatus } from '../plugins/status-rows.js';
7
+ import { signedInAccount } from '../extension/account.js';
8
+ import { printWarnings, summaryLines } from '../extension/row-lines.js';
9
+ import { gatherExtensions, invalidProfileError } from '../extension/rows.js';
10
+ import { gatherWithAccount } from './account-status.js';
10
11
  const mark = (good) => (good ? chalk.green('✓') : chalk.red('✗'));
11
12
  const row = (label, value) => {
12
13
  console.log(`${label.padEnd(10)} ${value}`);
@@ -50,60 +51,59 @@ export const printStatus = (report) => {
50
51
  if (report.update.message)
51
52
  console.log(chalk.yellow(`\n${report.update.message}`));
52
53
  };
53
- const defaultDeps = {
54
+ // `account` false: the report that closes setup, which reached the account already.
55
+ export const statusDeps = (account = true) => ({
54
56
  gather: (opts) => gatherStatus(resolveAuth({ token: opts.token }), siteFqdn()),
55
- plugins: () => (pluginsEnabled() ? gatherPluginStatus() : Promise.resolve(null)),
56
- };
57
- const settle = async (deps) => {
57
+ extensions: (opts) => account ? gatherWithAccount(signedInAccount({ token: opts.token })) : gatherExtensions(),
58
+ });
59
+ const settle = async (deps, opts) => {
58
60
  try {
59
- return { gathered: await deps.plugins(), error: null };
61
+ return { gathered: await deps.extensions(opts), error: null };
60
62
  }
61
63
  catch (error) {
62
64
  return { gathered: null, error };
63
65
  }
64
66
  };
67
+ // `--json` adds machine, profile, the extension count and the plan after the report's own keys;
68
+ // when the extension side failed, the report alone.
69
+ const jsonOf = (report, gathered) => {
70
+ if (!gathered)
71
+ return report;
72
+ const { machine, profile, extensions, plan } = gathered.status;
73
+ return { ...report, machine, profile, extensions: extensions.length, plan };
74
+ };
65
75
  const emit = (report, gathered, json) => {
66
- if (json) {
67
- console.log(JSON.stringify(gathered ? { ...report, ...gathered.status } : report, null, 2));
68
- return;
69
- }
76
+ if (json)
77
+ return console.log(JSON.stringify(jsonOf(report, gathered), null, 2));
70
78
  printStatus(report);
71
- for (const text of gathered ? pluginStatusLines(gathered.status) : [])
79
+ for (const text of gathered ? summaryLines(gathered.status) : [])
72
80
  console.log(text);
73
81
  };
74
- const profileFailure = (gathered) => {
75
- const profile = gathered?.status.profile;
76
- if (profile?.state !== 'invalid')
77
- return null;
78
- return new Error(`profile.json is invalid:\n${profile.errors.map((e) => ` ${e}`).join('\n')}`);
79
- };
80
- // The report always prints; a plugin-side failure then fails the step (exit 1) on stderr.
81
- export const runStatus = async (opts = {}, deps = defaultDeps) => {
82
+ // The report always prints; an extension-side failure then fails the step (exit 1) on stderr.
83
+ export const runStatus = async (opts = {}, deps = statusDeps()) => {
82
84
  const report = await deps.gather(opts);
83
- const { gathered, error } = await settle(deps);
85
+ const { gathered, error } = await settle(deps, opts);
84
86
  emit(report, gathered, opts.json);
85
- for (const warning of gathered?.warnings ?? [])
86
- console.error(chalk.yellow(`warning: ${warning}`));
87
+ printWarnings(gathered?.warnings ?? []);
87
88
  if (error)
88
89
  throw error;
89
- const failure = profileFailure(gathered);
90
+ const failure = gathered && invalidProfileError(gathered.status.profile);
90
91
  if (failure)
91
92
  throw failure;
92
93
  };
93
- const PLUGINS_HELP = `
94
- Plugins: one row per plugin in the profile or a dev path: id, state (installed, off,
95
- blocked, invalid, missing), package, installed version, wanted range, contributed commands,
96
- the last run from state/<id>.json, and the reason when it cannot load. Plugin code is never
97
- loaded; the only file written is machine.json.`;
94
+ const EXTENSIONS_HELP = `
95
+ Extensions: adds this machine's id and name (machine.json), the local profile version
96
+ (profile.json) with its source and, for a profile from your account, its etag and the
97
+ account's when that moved on, and the number of extensions, with what setup would change, e.g.
98
+ (1 to add); agentage extension list shows one row per extension. Signed in, it also marks this
99
+ machine seen in your account (--json: machine.registered). Extension code is never loaded; the
100
+ only file written is machine.json.`;
98
101
  export const registerStatus = (program, action) => {
99
- const command = program
102
+ program
100
103
  .command('status')
101
- .description(pluginsEnabled()
102
- ? 'Show CLI, account, endpoint, machine, profile, and plugin status'
103
- : 'Show CLI, account, and endpoint status')
104
+ .description('Show CLI, account, endpoint, machine, profile, and extension status')
104
105
  .option('--json', 'machine-readable output')
105
106
  .option('--token <token>', 'authenticate with a personal access token (aga_...); or set AGENTAGE_TOKEN')
107
+ .addHelpText('after', EXTENSIONS_HELP)
106
108
  .action(action);
107
- if (pluginsEnabled())
108
- command.addHelpText('after', PLUGINS_HELP);
109
109
  };
@@ -0,0 +1,8 @@
1
+ type Doc = Record<string, unknown>;
2
+ export declare const parseLock: (raw: string | null) => Doc | null;
3
+ export declare const lockEntries: (lock: Doc) => string[];
4
+ export declare const rootDependencies: (lock: Doc) => Record<string, string>;
5
+ export declare const lockedVersion: (lock: Doc | null, name: string) => string | null;
6
+ export declare const lockAgrees: (lock: Doc, dependencies: Record<string, string>) => boolean;
7
+ export declare const integrityDrift: (a: Doc, b: Doc) => string[];
8
+ export {};