@agentage/cli 0.40.0 → 0.41.1
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/README.md +23 -8
- package/dist/commands/extension/build.d.ts +6 -0
- package/dist/commands/extension/build.js +25 -0
- package/dist/commands/extension/delegate.d.ts +4 -2
- package/dist/commands/extension/delegate.js +19 -15
- package/dist/commands/extension/doctor.d.ts +13 -0
- package/dist/commands/extension/{check.js → doctor.js} +14 -6
- package/dist/commands/extension/install-load.js +10 -1
- package/dist/commands/extension/install.js +4 -1
- package/dist/commands/extension/kit-commands.d.ts +14 -0
- package/dist/commands/extension/kit-commands.js +42 -0
- package/dist/commands/extension/kit-tool.d.ts +6 -0
- package/dist/commands/extension/kit-tool.js +5 -0
- package/dist/commands/extension/latest.js +3 -2
- package/dist/commands/extension/list.d.ts +8 -3
- package/dist/commands/extension/list.js +35 -21
- package/dist/commands/extension/rows.d.ts +2 -0
- package/dist/commands/extension/rows.js +15 -9
- package/dist/commands/extension/up.d.ts +22 -0
- package/dist/commands/extension/up.js +80 -0
- package/dist/core/installer/compat.d.ts +10 -2
- package/dist/core/installer/compat.js +13 -4
- package/dist/core/installer/latest.d.ts +1 -0
- package/dist/core/installer/latest.js +7 -5
- package/dist/core/installer/npm.js +1 -1
- package/dist/core/installer/resolve.d.ts +1 -0
- package/dist/core/installer/resolve.js +6 -5
- package/dist/core/kit-version.d.ts +1 -0
- package/dist/core/kit-version.js +15 -0
- package/dist/core/loader/discover.d.ts +4 -0
- package/dist/core/loader/discover.js +6 -2
- package/dist/core/loader/kit-manifest.d.ts +9 -0
- package/dist/core/loader/kit-manifest.js +52 -0
- package/dist/core/loader/load.d.ts +2 -1
- package/dist/core/loader/load.js +21 -4
- package/dist/core/loader/manifest.d.ts +7 -1
- package/dist/core/loader/manifest.js +37 -6
- package/dist/core/loader/verbs.d.ts +15 -0
- package/dist/core/loader/verbs.js +57 -0
- package/dist/core/paths.d.ts +1 -0
- package/dist/core/paths.js +1 -0
- package/dist/program.d.ts +10 -2
- package/dist/program.js +21 -5
- package/package.json +2 -1
- package/dist/commands/extension/check.d.ts +0 -10
package/README.md
CHANGED
|
@@ -64,8 +64,14 @@ For extensions (see below):
|
|
|
64
64
|
| `agentage extension install <name> [--off]` | Add an extension to the profile, install and load it |
|
|
65
65
|
| `agentage extension remove <name> [--purge]` | Take it out of the profile and reinstall the rest |
|
|
66
66
|
| `agentage extension list` | One row per extension: state, version, commands, last run |
|
|
67
|
-
| `agentage extension
|
|
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 |
|
|
68
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.
|
|
69
75
|
|
|
70
76
|
Flags, exit codes and token auth are in [`docs/reference.md`](docs/reference.md).
|
|
71
77
|
|
|
@@ -95,11 +101,19 @@ agentage setup --check # exit 1 when this machine di
|
|
|
95
101
|
agentage setup --latest # newest versions the ranges allow, diff printed
|
|
96
102
|
agentage extension install @agentage/example # add one extension, install and load it
|
|
97
103
|
agentage extension install ./my-extension # a local folder, for development
|
|
104
|
+
agentage extension install . # link the working copy you are in
|
|
98
105
|
agentage extension remove @agentage/example # take it out again
|
|
99
106
|
agentage extension list # state per extension, with what setup would change
|
|
100
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
|
|
101
110
|
```
|
|
102
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
|
+
|
|
103
117
|
The plan is what `setup` would change, one line per extension (`+ add`, `~ update`, `- remove`,
|
|
104
118
|
`o off`) and a summary such as `plan: 1 to add, 0 to update, 0 to remove, 0 off, 2 unchanged`.
|
|
105
119
|
`setup --dry-run` prints it, `setup --check` exits 1 when anything would change, `extension list`
|
|
@@ -126,13 +140,14 @@ Extensions run in the CLI process with your privileges, so install only extensio
|
|
|
126
140
|
npm runs with `--ignore-scripts`, and the account token is removed from the environment before
|
|
127
141
|
any extension code loads. Everything lives in the config directory:
|
|
128
142
|
|
|
129
|
-
| Path
|
|
130
|
-
|
|
|
131
|
-
| `~/.agentage/profile.json`
|
|
132
|
-
| `~/.agentage/machine.json`
|
|
133
|
-
| `~/.agentage/extensions/`
|
|
134
|
-
| `~/.agentage/state/`
|
|
135
|
-
| `~/.agentage/
|
|
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 |
|
|
136
151
|
|
|
137
152
|
Every state an extension can be in, and every flag, is in [`docs/reference.md`](docs/reference.md).
|
|
138
153
|
To write an extension, start with [`docs/extensions.md`](docs/extensions.md).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Command } from 'commander';
|
|
2
|
+
import type { BuildOptions } from '@agentage/extension/tool';
|
|
3
|
+
import { type LoadKitTool } from './kit-tool.js';
|
|
4
|
+
export type ExtensionBuildOptions = Pick<BuildOptions, 'push' | 'dir' | 'registry'>;
|
|
5
|
+
export declare const runExtensionBuild: (opts?: ExtensionBuildOptions, tool?: LoadKitTool) => Promise<void>;
|
|
6
|
+
export declare const addBuild: (group: Command, build: (opts: ExtensionBuildOptions) => Promise<void>) => void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {} from 'commander';
|
|
2
|
+
import { loadKitTool } from './kit-tool.js';
|
|
3
|
+
// One line per image on stdout (part, tag, digest when pushed), then the record path.
|
|
4
|
+
export const runExtensionBuild = async (opts = {}, tool = loadKitTool) => {
|
|
5
|
+
const { build, buildLines } = await tool();
|
|
6
|
+
for (const line of buildLines(await build(opts)))
|
|
7
|
+
console.log(line);
|
|
8
|
+
};
|
|
9
|
+
const BUILD_HELP = `
|
|
10
|
+
Builds one image per server part of the extension package in --dir (default: the current
|
|
11
|
+
folder): the manifest is checked, the package's own build script runs, then the kit recipe makes
|
|
12
|
+
each image, tagged ghcr.io/agentage/<id>-<part>:<version> with the commit and build time as
|
|
13
|
+
labels. --push pushes them (run docker login first) and writes dist/agentage-extension.json, the
|
|
14
|
+
build record with every image by digest. Needs docker. Exits 1 when the manifest is invalid or a
|
|
15
|
+
build or push fails.`;
|
|
16
|
+
export const addBuild = (group, build) => {
|
|
17
|
+
group
|
|
18
|
+
.command('build')
|
|
19
|
+
.description('Build the part images of an extension package; --push pushes them')
|
|
20
|
+
.option('--push', 'push the images and write the build record with their digests')
|
|
21
|
+
.option('--dir <path>', 'the extension package folder (default: the current folder)')
|
|
22
|
+
.option('--registry <host/owner>', 'image registry and owner (default: ghcr.io/agentage)')
|
|
23
|
+
.addHelpText('after', BUILD_HELP)
|
|
24
|
+
.action(build);
|
|
25
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Command } from 'commander';
|
|
2
|
-
|
|
3
|
-
|
|
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;
|
|
4
6
|
export {};
|
|
@@ -3,10 +3,13 @@ import {} from '../../core/loader/discover.js';
|
|
|
3
3
|
import { loadExtensions } from '../../core/loader/load.js';
|
|
4
4
|
import { loadOrder } from '../../core/loader/order.js';
|
|
5
5
|
import { loadSet, scan } from '../../core/loader/scan.js';
|
|
6
|
+
import { claimVerbs, commandNames, readHeld } from '../../core/loader/verbs.js';
|
|
6
7
|
import { NO_MACHINE, readMachine } from '../../core/profile/machine.js';
|
|
8
|
+
import { registerKit, stub } from './kit-commands.js';
|
|
9
|
+
import { kitCheck, loadKitTool } from './kit-tool.js';
|
|
7
10
|
// Loads the command's extension and its peers only, then hands it the whole command line.
|
|
8
11
|
const runDelegated = async (ext, name, extensions, program, argv) => {
|
|
9
|
-
const session = await loadExtensions(loadOrder([ext], extensions), program);
|
|
12
|
+
const session = await loadExtensions(loadOrder([ext], extensions), program, kitCheck());
|
|
10
13
|
try {
|
|
11
14
|
const loaded = session.loaded.get(ext.id ?? '');
|
|
12
15
|
const run = session.outcomes.get(ext.pkg);
|
|
@@ -21,25 +24,26 @@ const runDelegated = async (ext, name, extensions, program, argv) => {
|
|
|
21
24
|
}
|
|
22
25
|
};
|
|
23
26
|
// Usage, exit 2: the core commands, then one line per extension with what it contributes.
|
|
24
|
-
const unknownCommand = (program, core, extensions) => (operands) => {
|
|
27
|
+
const unknownCommand = (program, core, extensions, verbs) => (operands) => {
|
|
25
28
|
const lines = [`error: unknown command '${operands[0]}'`, `core: ${core.join(', ')}`];
|
|
26
29
|
for (const ext of extensions.filter((e) => e.commands.length > 0))
|
|
27
|
-
lines.push(`${ext.id}: ${ext.
|
|
30
|
+
lines.push(`${ext.id}: ${commandNames(ext, verbs).join(', ')}`);
|
|
28
31
|
program.error(lines.join('\n'), { code: 'commander.unknownCommand', exitCode: 2 });
|
|
29
32
|
};
|
|
33
|
+
const registerApi = (program, ext, run) => {
|
|
34
|
+
for (const name of ext.commands)
|
|
35
|
+
stub(program, name, `from the ${ext.id} extension`, run(name)).helpGroup(`${ext.id}:`);
|
|
36
|
+
};
|
|
30
37
|
// Manifests only, never code: each stub hands the command line, --help included, to the extension.
|
|
31
|
-
export const registerDelegates = (program, argv, wrap) => {
|
|
38
|
+
export const registerDelegates = (program, argv, wrap, tool = loadKitTool) => {
|
|
32
39
|
const core = program.commands.map((command) => command.name());
|
|
33
40
|
const extensions = loadSet(scan(readMachine() ?? NO_MACHINE));
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.allowExcessArguments()
|
|
43
|
-
.action(wrap(() => runDelegated(ext, name, extensions, program, argv)));
|
|
44
|
-
program.on('command:*', unknownCommand(program, core, extensions));
|
|
41
|
+
const verbs = claimVerbs(extensions, readHeld());
|
|
42
|
+
for (const ext of extensions) {
|
|
43
|
+
if (ext.kind === 'kit')
|
|
44
|
+
registerKit(program, ext, verbs, { argv, wrap, tool });
|
|
45
|
+
else
|
|
46
|
+
registerApi(program, ext, (name) => wrap(() => runDelegated(ext, name, extensions, program, argv)));
|
|
47
|
+
}
|
|
48
|
+
program.on('command:*', unknownCommand(program, core, extensions, verbs));
|
|
45
49
|
};
|
|
@@ -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 {};
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import { driftLines } from '../../core/installer/plan.js';
|
|
2
3
|
import {} from '../../core/loader/discover.js';
|
|
3
4
|
import { loadExtensions } from '../../core/loader/load.js';
|
|
4
5
|
import { loadSet, scan } from '../../core/loader/scan.js';
|
|
5
6
|
import { loadMachine } from '../../core/profile/machine.js';
|
|
7
|
+
import { kitCheck } from './kit-tool.js';
|
|
6
8
|
import { printReport } from './list.js';
|
|
7
9
|
import { gatherFrom, invalidProfileError, } from './rows.js';
|
|
8
|
-
// Loads what can load
|
|
9
|
-
|
|
10
|
-
export const loadAndGather = async (beforeLoad = async () => undefined, tree) => {
|
|
10
|
+
// Loads what can load once (`tree`: that tree, not the current one); the kit checks kit-shaped ones.
|
|
11
|
+
export const loadAndGather = async (beforeLoad = async () => undefined, tree, tool) => {
|
|
11
12
|
const found = scan(await loadMachine(), tree);
|
|
12
13
|
const extensions = loadSet(found);
|
|
13
14
|
await beforeLoad(extensions);
|
|
14
|
-
const session = await loadExtensions(extensions);
|
|
15
|
+
const session = await loadExtensions(extensions, undefined, kitCheck(tool));
|
|
15
16
|
await session.dispose();
|
|
16
17
|
return gatherFrom(found, session.outcomes);
|
|
17
18
|
};
|
|
@@ -25,11 +26,18 @@ export const failedError = (rows) => {
|
|
|
25
26
|
// Integrity drift: the extensions it names were not loaded, and setup refuses to build on it.
|
|
26
27
|
const driftError = (drift) => drift.length === 0 ? null : new Error(driftLines(drift).join('\n'));
|
|
27
28
|
// The rows always print; an invalid profile.json, integrity drift or a failed load fails the step.
|
|
28
|
-
export const
|
|
29
|
-
const gathered = await loadAndGather();
|
|
29
|
+
export const runExtensionDoctor = async (opts = {}, tool) => {
|
|
30
|
+
const gathered = await loadAndGather(undefined, undefined, tool);
|
|
30
31
|
printReport(gathered, opts.json);
|
|
31
32
|
const { profile, extensions } = gathered.status;
|
|
32
33
|
const failure = invalidProfileError(profile) ?? driftError(gathered.drift) ?? failedError(extensions);
|
|
33
34
|
if (failure)
|
|
34
35
|
throw failure;
|
|
35
36
|
};
|
|
37
|
+
export const CHECK_DEPRECATED = 'agentage extension check is now agentage extension doctor; ' +
|
|
38
|
+
'in 90 days check becomes the conformance check';
|
|
39
|
+
// The old name for 90 days: one line on stderr, then doctor, so --json stdout stays the report.
|
|
40
|
+
export const deprecatedCheck = (doctor, writeErr) => async (opts) => {
|
|
41
|
+
writeErr(`${chalk.yellow(`warning: ${CHECK_DEPRECATED}`)}\n`);
|
|
42
|
+
await doctor(opts);
|
|
43
|
+
};
|
|
@@ -2,7 +2,8 @@ import { install } from '../../core/installer/installer.js';
|
|
|
2
2
|
import {} from '../../core/installer/npm.js';
|
|
3
3
|
import {} from '../../core/loader/discover.js';
|
|
4
4
|
import { missingSecrets } from '../../core/loader/secrets.js';
|
|
5
|
-
import {
|
|
5
|
+
import { writeHeld } from '../../core/loader/verbs.js';
|
|
6
|
+
import { failedError, loadAndGather } from './doctor.js';
|
|
6
7
|
import { printWarnings, rowLines } from './row-lines.js';
|
|
7
8
|
import { gatherExtensions } from './rows.js';
|
|
8
9
|
import { askSecrets } from './secrets-prompt.js';
|
|
@@ -14,6 +15,12 @@ const installLine = (result) => {
|
|
|
14
15
|
};
|
|
15
16
|
// Asks for, or lists, the secrets the extensions about to load declare and this machine lacks.
|
|
16
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
|
+
};
|
|
17
24
|
// Rows print either way, only a good install loads; `installed` runs before anything loads.
|
|
18
25
|
export const installAndLoad = async (set, npm, installed = async () => undefined) => {
|
|
19
26
|
const outcome = await install(set, npm).then((result) => ({ result, error: null }), (error) => ({ result: null, error }));
|
|
@@ -24,6 +31,8 @@ export const installAndLoad = async (set, npm, installed = async () => undefined
|
|
|
24
31
|
const gathered = outcome.result ? await loadAndGather(listSecrets) : await gatherExtensions();
|
|
25
32
|
for (const text of rowLines(gathered.status.extensions))
|
|
26
33
|
console.log(text);
|
|
34
|
+
if (outcome.result)
|
|
35
|
+
settleVerbs(gathered);
|
|
27
36
|
printWarnings(gathered.warnings);
|
|
28
37
|
if (outcome.error)
|
|
29
38
|
throw outcome.error;
|
|
@@ -21,7 +21,10 @@ const addDev = async (target, off, deps) => {
|
|
|
21
21
|
if (off)
|
|
22
22
|
throw new Error(`--off does not apply to ${target.path}: a dev extension always loads`);
|
|
23
23
|
await setDev(target.name, target.path);
|
|
24
|
-
|
|
24
|
+
if (target.kit)
|
|
25
|
+
console.log(line('linked', `${target.name} (working copy)`));
|
|
26
|
+
else
|
|
27
|
+
console.log(line('dev', `${target.name} ${target.path}`));
|
|
25
28
|
await installAndLoad(editableProfile(), deps.npm);
|
|
26
29
|
};
|
|
27
30
|
// Never signs in: the name is looked up in the registries the local profile routes.
|
|
@@ -0,0 +1,14 @@
|
|
|
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 stub: (parent: Command, name: string, summary: string, run: Run) => Command;
|
|
13
|
+
export declare const registerKit: (program: Command, ext: DiscoveredExtension, verbs: Verbs, { argv, wrap, tool }: KitRegistration) => void;
|
|
14
|
+
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, the rest of the line to the extension's own code.
|
|
17
|
+
export 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
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type * as Kit from '@agentage/extension/tool';
|
|
2
|
+
import { type KitCheck } from '../../core/loader/load.js';
|
|
3
|
+
export type KitTool = Pick<typeof Kit, 'build' | 'buildLines' | 'runCommand' | 'checkCli' | 'up' | 'down' | 'token'>;
|
|
4
|
+
export type LoadKitTool = () => Promise<KitTool>;
|
|
5
|
+
export declare const loadKitTool: LoadKitTool;
|
|
6
|
+
export declare const kitCheck: (tool?: LoadKitTool) => KitCheck;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import {} from '../../core/loader/load.js';
|
|
2
|
+
// Imported on use, so every other command starts without loading the kit.
|
|
3
|
+
export const loadKitTool = () => import('@agentage/extension/tool');
|
|
4
|
+
// doctor and every load check a kit-shaped extension's command entry through the kit.
|
|
5
|
+
export const kitCheck = (tool = loadKitTool) => async (dir) => (await tool()).checkCli(dir);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { needsText } from '../../core/installer/compat.js';
|
|
1
2
|
import {} from '../../core/installer/installer.js';
|
|
2
3
|
import { updateLatest, } from '../../core/installer/latest.js';
|
|
3
4
|
import {} from '../../core/installer/npm.js';
|
|
4
5
|
import { editableProfile, mutateProfile } from '../../core/profile/profile.js';
|
|
5
6
|
import { pushBody } from '../../core/profile/push.js';
|
|
6
|
-
import { loadAndGather } from './
|
|
7
|
+
import { loadAndGather } from './doctor.js';
|
|
7
8
|
import { line, listSecrets } from './install-load.js';
|
|
8
9
|
import { printWarnings, rowLines } from './row-lines.js';
|
|
9
10
|
import {} from './rows.js';
|
|
@@ -15,7 +16,7 @@ const changeLine = ({ name, from, to }) => {
|
|
|
15
16
|
return `- remove ${name} ${from}`;
|
|
16
17
|
return `~ update ${name} ${from} -> ${to}`;
|
|
17
18
|
};
|
|
18
|
-
const heldLine = ({ name, version, newer,
|
|
19
|
+
const heldLine = ({ name, version, newer, ...range }) => line('kept', `${name} ${version} (${newer} ${needsText(range)})`);
|
|
19
20
|
const printDiff = (result, tree) => {
|
|
20
21
|
for (const held of result.held)
|
|
21
22
|
console.log(heldLine(held));
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { type Command } from 'commander';
|
|
2
|
-
import { type
|
|
2
|
+
import { type ExtensionBuildOptions } from './build.js';
|
|
3
|
+
import { type ExtensionDoctorOptions } from './doctor.js';
|
|
3
4
|
import { type ExtensionInstallOptions } from './install.js';
|
|
4
5
|
import { type ExtensionRemoveOptions } from './remove.js';
|
|
5
6
|
import { type ExtensionsGather } from './rows.js';
|
|
7
|
+
import { type KitActions } from './up.js';
|
|
6
8
|
export interface ExtensionListOptions {
|
|
7
9
|
json?: boolean;
|
|
8
10
|
}
|
|
9
11
|
export declare const printReport: ({ status, warnings }: ExtensionsGather, json?: boolean) => void;
|
|
10
12
|
export declare const runExtensionList: (opts?: ExtensionListOptions) => Promise<void>;
|
|
11
|
-
export
|
|
13
|
+
export declare const helpOrUnknown: (_opts: unknown, group: Command) => void;
|
|
14
|
+
export interface ExtensionActions extends KitActions {
|
|
12
15
|
install: (opts: ExtensionInstallOptions) => Promise<void>;
|
|
13
16
|
remove: (opts: ExtensionRemoveOptions) => Promise<void>;
|
|
14
17
|
list: (opts: ExtensionListOptions) => Promise<void>;
|
|
15
|
-
|
|
18
|
+
doctor: (opts: ExtensionDoctorOptions) => Promise<void>;
|
|
19
|
+
check: (opts: ExtensionDoctorOptions) => Promise<void>;
|
|
20
|
+
build: (opts: ExtensionBuildOptions) => Promise<void>;
|
|
16
21
|
}
|
|
17
22
|
export declare const registerExtension: (program: Command, actions: ExtensionActions) => void;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {} from 'commander';
|
|
2
|
-
import {} from './
|
|
2
|
+
import { addBuild } from './build.js';
|
|
3
|
+
import {} from './doctor.js';
|
|
3
4
|
import {} from './install.js';
|
|
4
5
|
import {} from './remove.js';
|
|
5
6
|
import { printWarnings, rowLines } from './row-lines.js';
|
|
6
7
|
import { gatherExtensions, invalidProfileError } from './rows.js';
|
|
8
|
+
import { addKitTools } from './up.js';
|
|
7
9
|
// The rows, or the whole report as JSON, on stdout; warnings on stderr.
|
|
8
10
|
export const printReport = ({ status, warnings }, json) => {
|
|
9
11
|
const rows = status.extensions.length > 0 ? rowLines(status.extensions) : ['no extensions'];
|
|
@@ -23,18 +25,21 @@ export const runExtensionList = async (opts = {}) => {
|
|
|
23
25
|
throw failure;
|
|
24
26
|
};
|
|
25
27
|
const INSTALL_HELP = `
|
|
26
|
-
<name> is resolved in this order: a local path (./path or /path) is a
|
|
27
|
-
machine.json by its real path and loaded on this machine only, in place
|
|
28
|
-
the same package; http:, file:, npm: and github: are refused; an https:
|
|
29
|
-
package name are used as written (a name without @<range> is pinned to ^ its
|
|
30
|
-
whose engines.agentage fits this CLI's extension API,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
between, it is replayed once on that
|
|
28
|
+
<name> is resolved in this order: a local path (./path or /path) is a working copy, linked as a
|
|
29
|
+
dev extension: kept in machine.json by its real path and loaded on this machine only, in place
|
|
30
|
+
of an installed copy of the same package; http:, file:, npm: and github: are refused; an https:
|
|
31
|
+
tarball URL and a package name are used as written (a name without @<range> is pinned to ^ its
|
|
32
|
+
newest version whose engines.agentage fits this CLI's extension API, or the kit's version for a
|
|
33
|
+
package built on the extension kit, naming each newer version it skipped); a short name like
|
|
34
|
+
memory is @agentage/extension-memory, else agentage-extension-memory. A package goes into
|
|
35
|
+
profile.json (created from the default profile when absent), from the registry the profile
|
|
36
|
+
names for its scope; --off keeps it from loading on this machine. Then installs and loads like
|
|
37
|
+
setup, and exits 1 when an extension failed. Never signs in. On a machine whose profile comes
|
|
38
|
+
from your account, the change applies to the account's newest profile and is pushed (with npm's
|
|
39
|
+
lock) once everything loaded; if another machine pushed in between, it is replayed once on that
|
|
40
|
+
profile. The commands of a package built on the kit run as agentage <command> and always as
|
|
41
|
+
agentage <id> <command>; one whose name is a core command, an extension id or a command an
|
|
42
|
+
earlier install took runs only as agentage <id> <command>, and one line says so.`;
|
|
38
43
|
const REMOVE_HELP = `
|
|
39
44
|
<name> is a package name, a short name or an extension id. Refused while another extension
|
|
40
45
|
peer-depends on it. Takes it out of profile.json and this machine's off list, or out of
|
|
@@ -47,13 +52,15 @@ off, blocked, invalid, missing), what setup would change (add, update, remove; b
|
|
|
47
52
|
nothing), package, installed version, wanted range, contributed commands, the last run from
|
|
48
53
|
state/<id>.json, and the reason when it cannot load. Extension code is never loaded; the only
|
|
49
54
|
file written is machine.json.`;
|
|
50
|
-
const
|
|
55
|
+
const DOCTOR_HELP = `
|
|
51
56
|
Loads every extension this machine can run, once, the way setup does: what an extension prints
|
|
52
57
|
while loading goes to stderr, state/<id>.json is rewritten for each, and the rows show ok or
|
|
53
|
-
failed with the reason (a dependent of a failed extension is blocked).
|
|
54
|
-
extension
|
|
58
|
+
failed with the reason (a dependent of a failed extension is blocked). An extension built on the
|
|
59
|
+
extension kit is ok when its command entry exports create and its handlers are the commands it
|
|
60
|
+
declares. Exits 1 when any extension failed, or on integrity drift between the lock and the
|
|
61
|
+
installed tree. extension check is its old name, kept for 90 days.`;
|
|
55
62
|
// Bare `extension` prints its help (exit 0); an unknown subcommand stays a usage error (exit 2).
|
|
56
|
-
const helpOrUnknown = (_opts, group) => {
|
|
63
|
+
export const helpOrUnknown = (_opts, group) => {
|
|
57
64
|
const name = group.args[0];
|
|
58
65
|
if (name === undefined)
|
|
59
66
|
group.help();
|
|
@@ -76,7 +83,7 @@ const addChanges = (group, { install, remove }) => {
|
|
|
76
83
|
.addHelpText('after', REMOVE_HELP)
|
|
77
84
|
.action((name, opts) => remove({ name, ...opts }));
|
|
78
85
|
};
|
|
79
|
-
const addReports = (group, { list, check }) => {
|
|
86
|
+
const addReports = (group, { list, doctor, check }) => {
|
|
80
87
|
group
|
|
81
88
|
.command('list')
|
|
82
89
|
.description('Show one row per extension: state, versions, commands, last run')
|
|
@@ -84,13 +91,18 @@ const addReports = (group, { list, check }) => {
|
|
|
84
91
|
.addHelpText('after', LIST_HELP)
|
|
85
92
|
.action(list);
|
|
86
93
|
group
|
|
87
|
-
.command('
|
|
94
|
+
.command('doctor')
|
|
88
95
|
.description('Load every extension once and refresh its state: ok or failed')
|
|
89
96
|
.option('--json', 'machine-readable output')
|
|
90
|
-
.addHelpText('after',
|
|
97
|
+
.addHelpText('after', DOCTOR_HELP)
|
|
98
|
+
.action(doctor);
|
|
99
|
+
group
|
|
100
|
+
.command('check', { hidden: true })
|
|
101
|
+
.description('Deprecated: the old name of doctor')
|
|
102
|
+
.option('--json', 'machine-readable output')
|
|
91
103
|
.action(check);
|
|
92
104
|
};
|
|
93
|
-
// Help lists the subcommands in this order: install, remove, list,
|
|
105
|
+
// Help lists the subcommands in this order: install, remove, list, doctor, build, up, down, token.
|
|
94
106
|
export const registerExtension = (program, actions) => {
|
|
95
107
|
const group = program
|
|
96
108
|
.command('extension')
|
|
@@ -99,4 +111,6 @@ export const registerExtension = (program, actions) => {
|
|
|
99
111
|
.action(helpOrUnknown);
|
|
100
112
|
addChanges(group, actions);
|
|
101
113
|
addReports(group, actions);
|
|
114
|
+
addBuild(group, actions.build);
|
|
115
|
+
addKitTools(group, actions);
|
|
102
116
|
};
|
|
@@ -2,6 +2,7 @@ import { type PlanSummary, type PlanVerb } from '../../core/installer/plan.js';
|
|
|
2
2
|
import { type LoadOutcome } from '../../core/loader/load.js';
|
|
3
3
|
import { type Scan } from '../../core/loader/scan.js';
|
|
4
4
|
import { type StaticState } from '../../core/loader/states.js';
|
|
5
|
+
import { type Verbs } from '../../core/loader/verbs.js';
|
|
5
6
|
import { type ProfileSource } from '../../core/profile/machine.js';
|
|
6
7
|
export type RowState = StaticState | 'ok' | 'failed';
|
|
7
8
|
export interface ExtensionRow {
|
|
@@ -50,6 +51,7 @@ export interface ExtensionsGather {
|
|
|
50
51
|
status: ExtensionsStatus;
|
|
51
52
|
warnings: string[];
|
|
52
53
|
drift: string[];
|
|
54
|
+
verbs: Verbs;
|
|
53
55
|
}
|
|
54
56
|
export declare const gatherFrom: (found: Scan, runs?: ReadonlyMap<string, LoadOutcome>) => ExtensionsGather;
|
|
55
57
|
export declare const gatherExtensions: () => Promise<ExtensionsGather>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {} from '../../core/installer/plan.js';
|
|
2
2
|
import {} from '../../core/loader/discover.js';
|
|
3
3
|
import {} from '../../core/loader/load.js';
|
|
4
|
-
import { scan } from '../../core/loader/scan.js';
|
|
4
|
+
import { loadSet, scan } from '../../core/loader/scan.js';
|
|
5
5
|
import { readRunState } from '../../core/loader/state.js';
|
|
6
6
|
import {} from '../../core/loader/states.js';
|
|
7
|
+
import { claimVerbs, commandNames, readHeld } from '../../core/loader/verbs.js';
|
|
7
8
|
import { loadMachine } from '../../core/profile/machine.js';
|
|
8
9
|
import {} from '../../core/profile/schema.js';
|
|
9
10
|
const profileStatus = (parsed, machine) => {
|
|
@@ -21,7 +22,7 @@ const lastRunOf = (p, s) => {
|
|
|
21
22
|
const run = p.id === null || s.state === 'invalid' ? null : readRunState(p.id);
|
|
22
23
|
return run && { at: run.at, state: run.state, error: run.error };
|
|
23
24
|
};
|
|
24
|
-
const toRow = (p, found, s) => ({
|
|
25
|
+
const toRow = (p, { found, verbs }, s) => ({
|
|
25
26
|
id: p.id,
|
|
26
27
|
package: p.pkg,
|
|
27
28
|
mode: p.source === 'dev' ? 'dev' : 'installed',
|
|
@@ -30,7 +31,7 @@ const toRow = (p, found, s) => ({
|
|
|
30
31
|
state: s.state,
|
|
31
32
|
plan: p.source === 'dev' ? null : verbOf(found, p.pkg),
|
|
32
33
|
error: s.error,
|
|
33
|
-
commands: p
|
|
34
|
+
commands: commandNames(p, verbs),
|
|
34
35
|
lastRun: lastRunOf(p, s),
|
|
35
36
|
});
|
|
36
37
|
const verbOf = (found, pkg) => found.plan.extensions.find((entry) => entry.name === pkg)?.verb ?? null;
|
|
@@ -38,12 +39,17 @@ const verbOf = (found, pkg) => found.plan.extensions.find((entry) => entry.name
|
|
|
38
39
|
const withRun = (row, run) => run ? { ...row, state: run.state, error: run.error, lastRun: null } : row;
|
|
39
40
|
const UNKNOWN = { state: 'invalid', error: null };
|
|
40
41
|
const STILL_INSTALLED = { state: 'installed', error: null };
|
|
41
|
-
|
|
42
|
-
const {
|
|
43
|
-
|
|
44
|
-
...found.
|
|
45
|
-
...
|
|
42
|
+
const rowsOf = (seen, runs) => {
|
|
43
|
+
const { found, states, removed } = seen.found;
|
|
44
|
+
return [
|
|
45
|
+
...found.extensions.map((p) => withRun(toRow(p, seen, states.get(p.pkg) ?? UNKNOWN), runs.get(p.pkg))),
|
|
46
|
+
...removed.map((p) => toRow(p, seen, STILL_INSTALLED)),
|
|
46
47
|
].sort((a, b) => (a.package < b.package ? -1 : 1));
|
|
48
|
+
};
|
|
49
|
+
export const gatherFrom = (found, runs = new Map()) => {
|
|
50
|
+
const { machine, parsed } = found;
|
|
51
|
+
const seen = { found, verbs: claimVerbs(loadSet(found), readHeld()) };
|
|
52
|
+
const extensions = rowsOf(seen, runs);
|
|
47
53
|
const status = {
|
|
48
54
|
machine: { id: machine.id, name: machine.name },
|
|
49
55
|
profile: profileStatus(parsed, machine),
|
|
@@ -51,7 +57,7 @@ export const gatherFrom = (found, runs = new Map()) => {
|
|
|
51
57
|
extensions,
|
|
52
58
|
};
|
|
53
59
|
const warnings = [...(parsed?.ok ? parsed.warnings : []), ...found.found.warnings];
|
|
54
|
-
return { status, warnings, drift: found.drift };
|
|
60
|
+
return { status, warnings, drift: found.drift, verbs: seen.verbs };
|
|
55
61
|
};
|
|
56
62
|
// Reads manifests, profile and state files; writes only a missing machine.json.
|
|
57
63
|
export const gatherExtensions = async () => gatherFrom(scan(await loadMachine()));
|
|
@@ -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;
|