@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
package/README.md CHANGED
@@ -2,18 +2,19 @@
2
2
 
3
3
  ![Connect this machine to agentage from the terminal](https://github.com/agentage/cli/raw/master/docs/banner.svg)
4
4
 
5
- Connect this machine to your [agentage](https://agentage.io) account from the terminal. One
6
- command signs you in through your browser, and from then on the machine is known to your account.
5
+ Connect this machine to your [agentage](https://agentage.io) account from the terminal, and
6
+ install the extensions your account's profile names. One command signs you in through your
7
+ browser; the same profile then gives you the same extensions on every machine you set up.
7
8
 
8
9
  ## What is this?
9
10
 
10
- agentage gives your AI tools a shared, account-backed home. Every machine you use needs to be
11
- connected to that account once: this package does that connection. It keeps your sign-in on
12
- the machine (OAuth 2.1 with PKCE, nothing typed into the terminal), tells you whether the
13
- machine is connected, and keeps itself up to date.
11
+ agentage gives your AI tools a shared, account-backed home. Every machine you use is connected
12
+ to that account once, by this package. It keeps your sign-in on the machine (OAuth 2.1 with
13
+ PKCE, nothing typed into the terminal), tells you whether the machine is connected, and keeps
14
+ itself up to date.
14
15
 
15
- It is intentionally **small**: sign-in, status and update. Everything else arrives as plugins
16
- that the CLI installs for you.
16
+ The CLI itself stays **small**: sign-in, status, update, and an extension manager. Everything
17
+ else, memory included, arrives as an extension the CLI installs for you.
17
18
 
18
19
  ## Get started
19
20
 
@@ -33,7 +34,7 @@ agentage status
33
34
  ### On a server or in CI (no browser)
34
35
 
35
36
  ```bash
36
- agentage setup --no-browser # prints the sign-in URL to open elsewhere
37
+ agentage setup --no-browser # prints the sign-in URL to open elsewhere
37
38
  AGENTAGE_TOKEN=aga_... agentage status # or use a personal access token
38
39
  ```
39
40
 
@@ -44,34 +45,131 @@ AGENTAGE_TOKEN=aga_... agentage status # or use a personal access token
44
45
 
45
46
  ## Commands
46
47
 
47
- | Command | What it does |
48
- | ----------------------------- | ----------------------------------------------------------------------- |
49
- | `agentage setup` | Sign in through your browser, then show status |
50
- | `agentage setup --no-browser` | Print the sign-in URL instead of opening a browser |
51
- | `agentage setup --reauth` | Force a fresh sign-in |
52
- | `agentage setup --disconnect` | Sign out and remove local credentials |
53
- | `agentage status` | Version, update, target, sign-in state, endpoint (`--json` for scripts) |
54
- | `agentage update` | Install the latest published version (`--check` only reports) |
48
+ | Command | What it does |
49
+ | ----------------------------- | ----------------------------------------------------------------------------- |
50
+ | `agentage setup` | Sign in through your browser, install your account's profile, show status |
51
+ | `agentage setup --no-browser` | Print the sign-in URL instead of opening a browser |
52
+ | `agentage setup --reauth` | Force a fresh sign-in |
53
+ | `agentage setup --disconnect` | Sign out and remove local credentials |
54
+ | `agentage status` | Version, update, target, sign-in, endpoint, then machine, profile, extensions |
55
+ | `agentage update` | Install the latest published version (`--check` only reports) |
56
+
57
+ For extensions (see below):
58
+
59
+ | Command | What it does |
60
+ | -------------------------------------------- | ---------------------------------------------------------------- |
61
+ | `agentage setup --profile <file>` | Install and load the extensions a profile file names, no sign-in |
62
+ | `agentage setup --profile <file> --dry-run` | Show what would be installed, write nothing |
63
+ | `agentage setup --latest` | Update each extension to the newest version its range allows |
64
+ | `agentage extension install <name> [--off]` | Add an extension to the profile, install and load it |
65
+ | `agentage extension remove <name> [--purge]` | Take it out of the profile and reinstall the rest |
66
+ | `agentage extension list` | One row per extension: state, version, commands, last run |
67
+ | `agentage extension doctor` | Load every extension once and refresh the states |
68
+ | `agentage extension build [--push]` | Build the part images of an extension package; push with digests |
69
+ | `agentage extension up` / `down` | Run an extension package on this machine with Docker, then stop |
70
+ | `agentage extension token --scope <scope>` | Print a token from the dev issuer `extension up` started |
71
+ | `agentage <command>` | Run a command an installed extension contributes |
72
+ | `agentage <id> <command>` | The same for an extension built on the kit; always works |
73
+
74
+ `agentage extension check` is the old name of `doctor`, kept for 90 days.
55
75
 
56
76
  Flags, exit codes and token auth are in [`docs/reference.md`](docs/reference.md).
57
77
 
78
+ ## Extensions
79
+
80
+ An extension is an npm package that adds to the CLI: commands today, more later. Extensions are
81
+ on by default. A machine whose profile names no extensions has none, and `setup`, `status` and
82
+ `update` work as described above, with three more `status` lines: `machine`, `profile` and
83
+ `extensions`.
84
+
85
+ The model is simple. A **profile** says what this account wants: which extensions, at which
86
+ version ranges, and which ones are switched off on which machine. The CLI brings the machine
87
+ to that state: `setup` installs what the profile names, loads each extension once and prints
88
+ whether it loaded (`ok`) or why not. `extension install` and `extension remove` edit the
89
+ profile and then do the same. Nothing installs itself on a timer; the state only changes when
90
+ you run a command.
91
+
92
+ Once you are signed in, `setup` reads the profile your account holds, when it has one, so every
93
+ machine you set up gets the same extensions, and `extension install` and `extension remove` save
94
+ the change there. When your account has no profile to give, `setup` says so on one line and
95
+ carries on. Each machine you run `setup` on appears in your account as one of its machines.
96
+
97
+ ```bash
98
+ agentage setup --profile ./profile.json # install and load the extensions a profile names
99
+ agentage setup --profile ./profile.json --dry-run # show the plan, write nothing
100
+ agentage setup --check # exit 1 when this machine differs from its profile
101
+ agentage setup --latest # newest versions the ranges allow, diff printed
102
+ agentage extension install @agentage/example # add one extension, install and load it
103
+ agentage extension install ./my-extension # a local folder, for development
104
+ agentage extension install . # link the working copy you are in
105
+ agentage extension remove @agentage/example # take it out again
106
+ agentage extension list # state per extension, with what setup would change
107
+ agentage hello # a command the hello extension contributes
108
+ agentage ping # a command of an extension built on the kit
109
+ agentage probe ping # the same, by its extension id
110
+ ```
111
+
112
+ An extension built on the extension kit lists its commands in its manifest; each runs as
113
+ `agentage <command>` and always as `agentage <id> <command>`. When the name is a core command,
114
+ an extension id or a command an earlier install took, only `agentage <id> <command>` runs it, and
115
+ the install says so.
116
+
117
+ The plan is what `setup` would change, one line per extension (`+ add`, `~ update`, `- remove`,
118
+ `o off`) and a summary such as `plan: 1 to add, 0 to update, 0 to remove, 0 off, 2 unchanged`.
119
+ `setup --dry-run` prints it, `setup --check` exits 1 when anything would change, `extension list`
120
+ shows it per extension and `status` sums it up, for example `extensions 2 (1 to add)`.
121
+
122
+ `setup --latest` moves each extension to the newest version its range allows and prints one line
123
+ per change, such as `~ update @agentage/example 1.0.0 -> 1.0.1`. The new versions take over only
124
+ after every extension loaded from them cleanly; until then the installed ones stay. A version
125
+ that needs a newer extension API than this CLI has is skipped, here and by `extension install`.
126
+
127
+ A profile is a JSON file that maps package names to version ranges. `registries` is optional
128
+ and routes a scope to another npm-compatible registry; everything else comes from npm's public
129
+ registry.
130
+
131
+ ```json
132
+ {
133
+ "version": 1,
134
+ "extensions": { "@agentage/example": "^1.0.0" },
135
+ "registries": { "@acme": "https://npm.acme.example" }
136
+ }
137
+ ```
138
+
139
+ Extensions run in the CLI process with your privileges, so install only extensions you trust.
140
+ npm runs with `--ignore-scripts`, and the account token is removed from the environment before
141
+ any extension code loads. Everything lives in the config directory:
142
+
143
+ | Path | What |
144
+ | --------------------------- | -------------------------------------------------------------- |
145
+ | `~/.agentage/profile.json` | the profile: what this account wants |
146
+ | `~/.agentage/machine.json` | this machine's id, name, development extensions and account |
147
+ | `~/.agentage/extensions/` | installed extensions; `extensions/current` names the live tree |
148
+ | `~/.agentage/state/` | the last run of each extension |
149
+ | `~/.agentage/commands.json` | which extension holds each top-level command |
150
+ | `~/.agentage/data/<id>/` | what each extension keeps |
151
+
152
+ Every state an extension can be in, and every flag, is in [`docs/reference.md`](docs/reference.md).
153
+ To write an extension, start with [`docs/extensions.md`](docs/extensions.md).
154
+
58
155
  ## How it works
59
156
 
60
157
  This is the **terminal client for an agentage account**. `setup` registers a one-off OAuth
61
158
  client, opens the authorization page in your browser and waits on a localhost callback; the
62
159
  tokens land in `~/.agentage/auth.json` with owner-only permissions and are refreshed
63
160
  automatically. `status` validates the session against the account's introspection endpoint
64
- and reports version, update, target host and connectivity. Nothing runs in the background and
65
- nothing is installed beyond this package.
161
+ and reports version, update, target host and connectivity. Nothing runs in the background, and
162
+ nothing is installed beyond this package unless your profile names extensions.
66
163
 
67
164
  ## Configuration
68
165
 
69
- | Env | Default | Purpose |
70
- | --------------------- | ------------- | -------------------------------------------------- |
71
- | `AGENTAGE_SITE_FQDN` | `agentage.io` | Target host |
72
- | `AGENTAGE_CONFIG_DIR` | `~/.agentage` | Config and credentials directory |
73
- | `AGENTAGE_TOKEN` | - | Personal access token for CI and headless machines |
74
- | `NO_COLOR` | - | Disable colored output |
166
+ | Env | Default | Purpose |
167
+ | ------------------------- | --------------------- | -------------------------------------------------- |
168
+ | `AGENTAGE_SITE_FQDN` | `agentage.io` | Target host |
169
+ | `AGENTAGE_CONFIG_DIR` | `~/.agentage` | Config and credentials directory |
170
+ | `AGENTAGE_TOKEN` | - | Personal access token for CI and headless machines |
171
+ | `AGENTAGE_EXTENSIONS_DIR` | `<config>/extensions` | Where extensions are installed |
172
+ | `NO_COLOR` | - | Disable colored output |
75
173
 
76
174
  ## Develop
77
175
 
@@ -81,6 +179,9 @@ npm run verify
81
179
  npm run build && npm run test:e2e -- --grep @offline
82
180
  ```
83
181
 
182
+ The e2e specs under `e2e/core/` read as scenarios: each title names the command and what must
183
+ hold. They run against a loopback registry that serves the extensions under `fixtures/`.
184
+
84
185
  ## Release
85
186
 
86
187
  Releases publish to npm via GitHub Actions when a `chore(release): X.Y.Z` commit that touches
@@ -2,11 +2,17 @@ import { type Command } from 'commander';
2
2
  import { type SignInOptions, type SignInResult } from '../../lib/auth/sign-in.js';
3
3
  export interface SetupOptions extends SignInOptions {
4
4
  disconnect?: boolean;
5
+ profile?: string;
6
+ dryRun?: boolean;
7
+ check?: boolean;
8
+ latest?: boolean;
9
+ json?: boolean;
5
10
  }
6
11
  export interface SetupDeps {
7
12
  ensureSignedIn: (opts: SignInOptions) => Promise<SignInResult>;
8
13
  signOut: () => Promise<void>;
9
- printStatus: () => Promise<void>;
14
+ printStatus: (opts?: {
15
+ token?: string;
16
+ }) => Promise<void>;
10
17
  }
11
- export declare const runSetup: (opts: SetupOptions, deps: SetupDeps) => Promise<void>;
12
18
  export declare const registerSetup: (program: Command, action: (opts: SetupOptions) => Promise<void>) => void;
@@ -1,11 +1,36 @@
1
- import {} from 'commander';
1
+ import { Option } from 'commander';
2
2
  import {} from '../../lib/auth/sign-in.js';
3
- export const runSetup = async (opts, deps) => {
4
- if (opts.disconnect)
5
- return deps.signOut();
6
- await deps.ensureSignedIn({ reauth: opts.reauth, browser: opts.browser });
7
- await deps.printStatus();
8
- };
3
+ const EXTENSIONS_HELP = `
4
+ Extensions: --profile <file> validates a profile (schema v1), saves it as profile.json and
5
+ installs its extensions with npm, without signing in. --dry-run prints the plan, what setup
6
+ would change (+ add, ~ update, - remove, o off, then one summary line), and writes nothing;
7
+ --check prints the plan and exits 1 when anything would change, the lock in the extensions
8
+ directory does not match the installed tree, or machine.json is missing; it writes nothing.
9
+ Neither signs in. Plain setup signs in, then pulls the profile from your account (creating it
10
+ from this machine's profile.json, or the default, when the account has none), registers this
11
+ machine in the account and installs; it never pushes a change. When the account cannot be
12
+ reached it installs from the local profile.json, or, with none, installs nothing and says so on
13
+ one line. A profile from --profile stays local: setup neither pulls nor pushes it. Each package
14
+ comes from the registry the profile's registries map names for its scope (default
15
+ https://registry.npmjs.org) into AGENTAGE_EXTENSIONS_DIR (default ~/.agentage/extensions). After
16
+ installing, setup lists the secrets extensions declare and this machine has not set (asking for
17
+ them on a terminal), then loads every extension once and prints ok or failed for each; it exits
18
+ 1 when any failed. Signed in to another account than the one this machine is connected to, setup
19
+ exits 1; run setup --disconnect first, which also marks this machine disconnected in your
20
+ account.
21
+
22
+ --latest runs npm update in a staged copy of the installed tree: each extension moves to the
23
+ newest version its range allows, unless that version needs another extension API (then it is
24
+ kept, saying so). It prints one line per changed extension and a summary, loads the new tree and
25
+ makes it current only when every extension loaded clean, then pushes the profile with the new
26
+ lock to your account when the profile comes from it. With nothing newer it only prints
27
+ "latest: nothing to update". It never signs in.`;
28
+ const latestOption = () => new Option('--latest', 'update extensions to the newest versions their ranges allow').conflicts([
29
+ 'profile',
30
+ 'dryRun',
31
+ 'check',
32
+ 'disconnect',
33
+ ]);
9
34
  export const registerSetup = (program, action) => {
10
35
  program
11
36
  .command('setup')
@@ -13,5 +38,11 @@ export const registerSetup = (program, action) => {
13
38
  .option('--disconnect', 'sign out and remove local credentials')
14
39
  .option('--reauth', 'force a fresh sign-in')
15
40
  .option('--no-browser', 'print the sign-in URL instead of opening a browser')
41
+ .option('--profile <file>', 'install the extensions a profile file names; never signs in')
42
+ .option('--dry-run', 'print what setup would change and write nothing')
43
+ .option('--check', 'exit 1 when setup would change anything; write nothing')
44
+ .addOption(latestOption())
45
+ .option('--json', 'with --dry-run or --check: the plan as JSON')
46
+ .addHelpText('after', EXTENSIONS_HELP)
16
47
  .action(action);
17
48
  };
@@ -0,0 +1,5 @@
1
+ import { type Account } from '../../core/profile/cloud.js';
2
+ export type AccountOf = (opts?: {
3
+ token?: string;
4
+ }) => Account | null;
5
+ export declare const signedInAccount: AccountOf;
@@ -0,0 +1,18 @@
1
+ import {} from '../../core/profile/cloud.js';
2
+ import { authedFetch } from '../../lib/auth/api.js';
3
+ import { resolveAuth } from '../../lib/auth/credentials.js';
4
+ import { detectEnvMismatch } from '../../lib/auth/env-match.js';
5
+ import { introspectToken } from '../../lib/auth/introspect.js';
6
+ import { apiOrigin, links, siteFqdn } from '../../lib/net/origins.js';
7
+ const CLIENT = { 'x-client-type': 'cli' };
8
+ // Read before any extension loads: the token is captured here, the fetch at CLI start.
9
+ export const signedInAccount = (opts = {}) => {
10
+ const auth = resolveAuth(opts);
11
+ const fqdn = siteFqdn();
12
+ if (auth === null || detectEnvMismatch(auth, fqdn) !== null)
13
+ return null;
14
+ const target = links(fqdn);
15
+ const fetch = (path, { headers, timeoutMs, ...init } = {}) => authedFetch(auth, target, `${apiOrigin(fqdn)}${path}`, { ...init, headers: { ...CLIENT, ...headers } }, { timeoutMs });
16
+ const id = () => introspectToken(auth, target).then((s) => s.userId ?? null, () => null);
17
+ return { fetch, id };
18
+ };
@@ -0,0 +1,10 @@
1
+ import { type Command } from 'commander';
2
+ import { type BuildOptions, type BuildResult } from '@agentage/extension/tool';
3
+ export type ExtensionBuildOptions = Pick<BuildOptions, 'push' | 'dir' | 'registry'>;
4
+ export interface BuildTool {
5
+ build: (options: BuildOptions) => Promise<BuildResult>;
6
+ buildLines: (result: BuildResult) => string[];
7
+ }
8
+ export declare const loadBuildTool: () => Promise<BuildTool>;
9
+ export declare const runExtensionBuild: (opts?: ExtensionBuildOptions, tool?: () => Promise<BuildTool>) => Promise<void>;
10
+ export declare const addBuild: (group: Command, build: (opts: ExtensionBuildOptions) => Promise<void>) => void;
@@ -0,0 +1,27 @@
1
+ import {} from 'commander';
2
+ import {} from '@agentage/extension/tool';
3
+ // Imported on use, so every other command starts without loading the kit.
4
+ export const loadBuildTool = () => import('@agentage/extension/tool');
5
+ // One line per image on stdout (part, tag, digest when pushed), then the record path.
6
+ export const runExtensionBuild = async (opts = {}, tool = loadBuildTool) => {
7
+ const { build, buildLines } = await tool();
8
+ for (const line of buildLines(await build(opts)))
9
+ console.log(line);
10
+ };
11
+ const BUILD_HELP = `
12
+ Builds one image per server part of the extension package in --dir (default: the current
13
+ folder): the manifest is checked, the package's own build script runs, then the kit recipe makes
14
+ each image, tagged ghcr.io/agentage/<id>-<part>:<version> with the commit and build time as
15
+ labels. --push pushes them (run docker login first) and writes dist/agentage-extension.json, the
16
+ build record with every image by digest. Needs docker. Exits 1 when the manifest is invalid or a
17
+ build or push fails.`;
18
+ export const addBuild = (group, build) => {
19
+ group
20
+ .command('build')
21
+ .description('Build the part images of an extension package; --push pushes them')
22
+ .option('--push', 'push the images and write the build record with their digests')
23
+ .option('--dir <path>', 'the extension package folder (default: the current folder)')
24
+ .option('--registry <host/owner>', 'image registry and owner (default: ghcr.io/agentage)')
25
+ .addHelpText('after', BUILD_HELP)
26
+ .action(build);
27
+ };
@@ -0,0 +1,6 @@
1
+ import { type Command } from 'commander';
2
+ import { type LoadKitTool } from './kit-tool.js';
3
+ type Run = (opts: unknown) => Promise<void>;
4
+ type Wrap = (fn: () => Promise<void>) => Run;
5
+ export declare const registerDelegates: (program: Command, argv: string[], wrap: Wrap, tool?: LoadKitTool) => void;
6
+ export {};
@@ -0,0 +1,57 @@
1
+ import {} from 'commander';
2
+ import {} from '../../core/loader/discover.js';
3
+ import { loadExtensions } from '../../core/loader/load.js';
4
+ import { loadOrder } from '../../core/loader/order.js';
5
+ import { loadSet, scan } from '../../core/loader/scan.js';
6
+ import { claimVerbs, commandNames, readHeld } from '../../core/loader/verbs.js';
7
+ import { NO_MACHINE, readMachine } from '../../core/profile/machine.js';
8
+ import { registerKit } from './kit-commands.js';
9
+ import { kitCheck, loadKitTool } from './kit-tool.js';
10
+ // Loads the command's extension and its peers only, then hands it the whole command line.
11
+ const runDelegated = async (ext, name, extensions, program, argv) => {
12
+ const order = loadOrder([ext], extensions);
13
+ const session = await loadExtensions(order, program, kitCheck());
14
+ try {
15
+ const loaded = session.loaded.get(ext.id ?? '');
16
+ const run = session.outcomes.get(ext.pkg);
17
+ if (!loaded)
18
+ throw new Error(`extension ${ext.id} ${run?.state}: ${run?.error}`);
19
+ if (!loaded.program.commands.some((command) => command.name() === name))
20
+ throw new Error(`extension ${ext.id} did not add the command ${name}`);
21
+ await loaded.program.parseAsync(argv);
22
+ }
23
+ finally {
24
+ await session.dispose();
25
+ }
26
+ };
27
+ // Usage, exit 2: the core commands, then one line per extension with what it contributes.
28
+ const unknownCommand = (program, core, extensions, verbs) => (operands) => {
29
+ const lines = [`error: unknown command '${operands[0]}'`, `core: ${core.join(', ')}`];
30
+ for (const ext of extensions.filter((e) => e.commands.length > 0))
31
+ lines.push(`${ext.id}: ${commandNames(ext, verbs).join(', ')}`);
32
+ program.error(lines.join('\n'), { code: 'commander.unknownCommand', exitCode: 2 });
33
+ };
34
+ const registerApi = (program, ext, run) => {
35
+ for (const name of ext.commands)
36
+ program
37
+ .command(name)
38
+ .description(`from the ${ext.id} extension`)
39
+ .helpGroup(`${ext.id}:`)
40
+ .helpOption(false)
41
+ .allowUnknownOption()
42
+ .allowExcessArguments()
43
+ .action(run(name));
44
+ };
45
+ // Manifests only, never code: each stub hands the command line, --help included, to the extension.
46
+ export const registerDelegates = (program, argv, wrap, tool = loadKitTool) => {
47
+ const core = program.commands.map((command) => command.name());
48
+ const extensions = loadSet(scan(readMachine() ?? NO_MACHINE));
49
+ const verbs = claimVerbs(extensions, readHeld());
50
+ for (const ext of extensions) {
51
+ if (ext.kind === 'kit')
52
+ registerKit(program, ext, verbs, { argv, wrap, tool });
53
+ else
54
+ registerApi(program, ext, (name) => wrap(() => runDelegated(ext, name, extensions, program, argv)));
55
+ }
56
+ program.on('command:*', unknownCommand(program, core, extensions, verbs));
57
+ };
@@ -0,0 +1,13 @@
1
+ import { type DiscoveredExtension } from '../../core/loader/discover.js';
2
+ import { type LoadKitTool } from './kit-tool.js';
3
+ import { type ExtensionRow, type ExtensionsGather } from './rows.js';
4
+ export interface ExtensionDoctorOptions {
5
+ json?: boolean;
6
+ }
7
+ type BeforeLoad = (extensions: DiscoveredExtension[]) => Promise<void>;
8
+ export declare const loadAndGather: (beforeLoad?: BeforeLoad, tree?: string, tool?: LoadKitTool) => Promise<ExtensionsGather>;
9
+ export declare const failedError: (rows: ExtensionRow[]) => Error | null;
10
+ export declare const runExtensionDoctor: (opts?: ExtensionDoctorOptions, tool?: LoadKitTool) => Promise<void>;
11
+ export declare const CHECK_DEPRECATED: string;
12
+ export declare const deprecatedCheck: (doctor: (opts: ExtensionDoctorOptions) => Promise<void>, writeErr: (text: string) => void) => (opts: ExtensionDoctorOptions) => Promise<void>;
13
+ export {};
@@ -0,0 +1,44 @@
1
+ import chalk from 'chalk';
2
+ import { driftLines } from '../../core/installer/plan.js';
3
+ import {} from '../../core/loader/discover.js';
4
+ import { loadExtensions } from '../../core/loader/load.js';
5
+ import { loadSet, scan } from '../../core/loader/scan.js';
6
+ import { loadMachine } from '../../core/profile/machine.js';
7
+ import { kitCheck } from './kit-tool.js';
8
+ import { printReport } from './list.js';
9
+ import { gatherFrom, invalidProfileError, } from './rows.js';
10
+ // Loads what can load, once: state files rewritten, rows carrying this run's ok and failed.
11
+ // `tree` loads that tree instead of the current one; kit-shaped ones are checked by the kit.
12
+ export const loadAndGather = async (beforeLoad = async () => undefined, tree, tool) => {
13
+ const found = scan(await loadMachine(), tree);
14
+ const extensions = loadSet(found);
15
+ await beforeLoad(extensions);
16
+ const session = await loadExtensions(extensions, undefined, kitCheck(tool));
17
+ await session.dispose();
18
+ return gatherFrom(found, session.outcomes);
19
+ };
20
+ export const failedError = (rows) => {
21
+ const failed = rows.filter((row) => row.state === 'failed').map((row) => row.id ?? row.package);
22
+ if (failed.length === 0)
23
+ return null;
24
+ const count = `${failed.length} extension${failed.length === 1 ? '' : 's'}`;
25
+ return new Error(`${count} failed to load: ${failed.join(', ')}`);
26
+ };
27
+ // Integrity drift: the extensions it names were not loaded, and setup refuses to build on it.
28
+ const driftError = (drift) => drift.length === 0 ? null : new Error(driftLines(drift).join('\n'));
29
+ // The rows always print; an invalid profile.json, integrity drift or a failed load fails the step.
30
+ export const runExtensionDoctor = async (opts = {}, tool) => {
31
+ const gathered = await loadAndGather(undefined, undefined, tool);
32
+ printReport(gathered, opts.json);
33
+ const { profile, extensions } = gathered.status;
34
+ const failure = invalidProfileError(profile) ?? driftError(gathered.drift) ?? failedError(extensions);
35
+ if (failure)
36
+ throw failure;
37
+ };
38
+ export const CHECK_DEPRECATED = 'agentage extension check is now agentage extension doctor; ' +
39
+ 'in 90 days check becomes the conformance check';
40
+ // The old name for 90 days: one line on stderr, then doctor, so --json stdout stays the report.
41
+ export const deprecatedCheck = (doctor, writeErr) => async (opts) => {
42
+ writeErr(`${chalk.yellow(`warning: ${CHECK_DEPRECATED}`)}\n`);
43
+ await doctor(opts);
44
+ };
@@ -0,0 +1,7 @@
1
+ import { type InstallSet } from '../../core/installer/installer.js';
2
+ import { type Npm } from '../../core/installer/npm.js';
3
+ import { type DiscoveredExtension } from '../../core/loader/discover.js';
4
+ export declare const line: (label: string, value: string) => string;
5
+ export declare const counted: (n: number) => string;
6
+ export declare const listSecrets: (extensions: DiscoveredExtension[]) => Promise<void>;
7
+ export declare const installAndLoad: (set: InstallSet, npm: () => Promise<Npm>, installed?: () => Promise<unknown>) => Promise<void>;
@@ -0,0 +1,42 @@
1
+ import { install } from '../../core/installer/installer.js';
2
+ import {} from '../../core/installer/npm.js';
3
+ import {} from '../../core/loader/discover.js';
4
+ import { missingSecrets } from '../../core/loader/secrets.js';
5
+ import { writeHeld } from '../../core/loader/verbs.js';
6
+ import { failedError, loadAndGather } from './doctor.js';
7
+ import { printWarnings, rowLines } from './row-lines.js';
8
+ import { gatherExtensions } from './rows.js';
9
+ import { askSecrets } from './secrets-prompt.js';
10
+ export const line = (label, value) => `${label.padEnd(10)} ${value}`;
11
+ export const counted = (n) => n === 0 ? 'no extensions' : `${n} extension${n === 1 ? '' : 's'}`;
12
+ const installLine = (result) => {
13
+ const how = result.reused ? 'reused' : result.npm ? `npm ${result.npm}` : 'npm skipped';
14
+ return line('install', `${counted(result.count)} in tree ${result.tree} (${how})`);
15
+ };
16
+ // Asks for, or lists, the secrets the extensions about to load declare and this machine lacks.
17
+ export const listSecrets = (extensions) => askSecrets(missingSecrets(extensions));
18
+ // Who holds each top-level verb is kept for the next install; one line per namespaced command.
19
+ const settleVerbs = ({ verbs }) => {
20
+ writeHeld(verbs.top);
21
+ for (const { id, name, why } of verbs.namespaced)
22
+ console.log(line('namespaced', `agentage ${id} ${name} (agentage ${name} ${why})`));
23
+ };
24
+ // Rows print either way, only a good install loads; `installed` runs before anything loads.
25
+ export const installAndLoad = async (set, npm, installed = async () => undefined) => {
26
+ const outcome = await install(set, npm).then((result) => ({ result, error: null }), (error) => ({ result: null, error }));
27
+ if (outcome.result)
28
+ console.log(installLine(outcome.result));
29
+ if (outcome.result)
30
+ await installed();
31
+ const gathered = outcome.result ? await loadAndGather(listSecrets) : await gatherExtensions();
32
+ for (const text of rowLines(gathered.status.extensions))
33
+ console.log(text);
34
+ if (outcome.result)
35
+ settleVerbs(gathered);
36
+ printWarnings(gathered.warnings);
37
+ if (outcome.error)
38
+ throw outcome.error;
39
+ const failed = failedError(gathered.status.extensions);
40
+ if (failed)
41
+ throw failed;
42
+ };
@@ -0,0 +1,10 @@
1
+ import { type NpmManifest } from '../../core/installer/npm.js';
2
+ import { type EditDeps } from './sync.js';
3
+ export interface ExtensionInstallOptions {
4
+ name: string;
5
+ off?: boolean;
6
+ }
7
+ export interface ExtensionInstallDeps extends EditDeps {
8
+ manifest: () => Promise<NpmManifest>;
9
+ }
10
+ export declare const runExtensionInstall: (opts: ExtensionInstallOptions, deps: ExtensionInstallDeps) => Promise<void>;
@@ -0,0 +1,38 @@
1
+ import { needs } from '../../core/installer/compat.js';
2
+ import {} from '../../core/installer/npm.js';
3
+ import { registryLookup, resolveName } from '../../core/installer/resolve.js';
4
+ import { addExtension } from '../../core/profile/edit.js';
5
+ import { loadMachine, setDev } from '../../core/profile/machine.js';
6
+ import { editableProfile } from '../../core/profile/profile.js';
7
+ import {} from '../../core/profile/schema.js';
8
+ import { installAndLoad, line } from './install-load.js';
9
+ import { finishEdit, pullForEdit } from './sync.js';
10
+ const addPackage = async (target, off, deps) => {
11
+ for (const newer of target.skipped)
12
+ console.log(line('skipped', needs(newer)));
13
+ const synced = await pullForEdit(deps);
14
+ const { id } = await loadMachine();
15
+ const note = line('added', `${target.name} ${target.spec}${off ? ' (off on this machine)' : ''}`);
16
+ const add = (profile) => addExtension(profile, target.name, target.spec, id, off);
17
+ await finishEdit(synced, add, deps, note);
18
+ };
19
+ // A dev extension is this machine's own: it replaces an installed copy, and never syncs.
20
+ const addDev = async (target, off, deps) => {
21
+ if (off)
22
+ throw new Error(`--off does not apply to ${target.path}: a dev extension always loads`);
23
+ await setDev(target.name, target.path);
24
+ if (target.kit)
25
+ console.log(line('linked', `${target.name} (working copy)`));
26
+ else
27
+ console.log(line('dev', `${target.name} ${target.path}`));
28
+ await installAndLoad(editableProfile(), deps.npm);
29
+ };
30
+ // Never signs in: the name is looked up in the registries the local profile routes.
31
+ export const runExtensionInstall = async (opts, deps) => {
32
+ const { registries } = editableProfile();
33
+ const target = await resolveName(opts.name, registryLookup(deps.manifest, registries));
34
+ if (target.kind === 'dev')
35
+ await addDev(target, opts.off === true, deps);
36
+ else
37
+ await addPackage(target, opts.off === true, deps);
38
+ };
@@ -0,0 +1,13 @@
1
+ import { type Command } from 'commander';
2
+ import { type DiscoveredExtension } from '../../core/loader/discover.js';
3
+ import { type Verbs } from '../../core/loader/verbs.js';
4
+ import { type LoadKitTool } from './kit-tool.js';
5
+ export declare const KIT_EXIT = "agentage.kitExit";
6
+ type Run = (opts: unknown) => Promise<void>;
7
+ export interface KitRegistration {
8
+ argv: string[];
9
+ wrap: (fn: () => Promise<void>) => Run;
10
+ tool: LoadKitTool;
11
+ }
12
+ export declare const registerKit: (program: Command, ext: DiscoveredExtension, verbs: Verbs, { argv, wrap, tool }: KitRegistration) => void;
13
+ export {};
@@ -0,0 +1,42 @@
1
+ import { CommanderError } from 'commander';
2
+ import {} from '../../core/loader/discover.js';
3
+ import {} from '../../core/loader/verbs.js';
4
+ import {} from './kit-tool.js';
5
+ import { helpOrUnknown } from './list.js';
6
+ // A kit command's own exit code: the program returns it as the CLI's.
7
+ export const KIT_EXIT = 'agentage.kitExit';
8
+ // The kit parses args and owns --json and --help; the token never reaches extension code.
9
+ const runKit = async (tool, dir, command, argv) => {
10
+ const { runCommand } = await tool();
11
+ delete process.env['AGENTAGE_TOKEN'];
12
+ const code = await runCommand({ dir, command, argv });
13
+ if (code !== 0)
14
+ throw new CommanderError(code, KIT_EXIT, `${command} exited ${code}`);
15
+ };
16
+ // A stub per command: help from the manifest, everything after its own words to the kit.
17
+ const stub = (parent, name, summary, run) => parent
18
+ .command(name)
19
+ .description(summary)
20
+ .helpOption(false)
21
+ .allowUnknownOption()
22
+ .allowExcessArguments()
23
+ .action(run);
24
+ // `agentage {id}` alone prints its commands from the manifest; an unknown one is usage.
25
+ const groupOf = (program, id) => program
26
+ .command(id)
27
+ .description(`the commands of the ${id} extension`)
28
+ .helpGroup(`${id}:`)
29
+ .helpCommand(false)
30
+ .allowExcessArguments()
31
+ .action(helpOrUnknown);
32
+ // `agentage {id} {command}` always; `agentage {command}` where the verb is its own.
33
+ export const registerKit = (program, ext, verbs, { argv, wrap, tool }) => {
34
+ const id = ext.id ?? '';
35
+ const group = groupOf(program, id);
36
+ const run = (name, words) => wrap(() => runKit(tool, ext.dir, name, argv.slice(2 + words)));
37
+ for (const { name, summary } of ext.kitCommands) {
38
+ stub(group, name, summary, run(name, 2));
39
+ if (verbs.top.get(name) === ext.pkg)
40
+ stub(program, name, summary, run(name, 1)).helpGroup(`${id}:`);
41
+ }
42
+ };