@aiwg/cli 2026.8.7 → 2026.8.10
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/THIRD_PARTY_NOTICES.md +35 -0
- package/agentic/code/providers/capability-matrix.yaml +3 -3
- package/bin/aiwg.mjs +125 -0
- package/dist/src/artifacts/backends/graphology-backend.js +4 -3
- package/dist/src/artifacts/backends/sqlite-backend.js +4 -5
- package/dist/src/artifacts/cli.js +2 -2
- package/dist/src/artifacts/corpus-tools/cli.js +27 -0
- package/dist/src/artifacts/corpus-tools/profile-embed.js +3 -2
- package/dist/src/artifacts/corpus-tools/retrieval-lab.js +356 -0
- package/dist/src/artifacts/discover-facets.js +2 -2
- package/dist/src/artifacts/embedding-index.js +9 -8
- package/dist/src/artifacts/graph-backend.js +2 -2
- package/dist/src/artifacts/move.js +40 -2
- package/dist/src/artifacts/query-engine.js +21 -7
- package/dist/src/artifacts/repair.js +47 -0
- package/dist/src/artifacts/types.js +3 -3
- package/dist/src/cli/command-log.js +2 -2
- package/dist/src/cli/handlers/artifacts.js +50 -1
- package/dist/src/cli/handlers/cost-report.js +71 -0
- package/dist/src/cli/handlers/evidence.js +78 -0
- package/dist/src/cli/handlers/help.js +9 -0
- package/dist/src/cli/handlers/index.js +8 -3
- package/dist/src/cli/handlers/local-executor.js +4 -3
- package/dist/src/cli/handlers/refresh.js +20 -8
- package/dist/src/cli/handlers/regenerate.js +22 -12
- package/dist/src/cli/handlers/serve.js +15 -36
- package/dist/src/cli/handlers/sessions.js +5 -4
- package/dist/src/cli/handlers/setup-manifest.js +8 -1
- package/dist/src/cli/handlers/subcommands.js +30 -0
- package/dist/src/cli/handlers/use.js +272 -89
- package/dist/src/cli/handlers/utilities.js +149 -0
- package/dist/src/cli/handlers/workspace.js +10 -0
- package/dist/src/cli/help-generator.js +2 -1
- package/dist/src/cli/regenerate-selector.js +94 -0
- package/dist/src/cli/router.js +4 -1
- package/dist/src/cli/services/deployment-verification.js +596 -0
- package/dist/src/cli/skill-usage.js +2 -2
- package/dist/src/cli/workflow-orchestrator.js +1 -1
- package/dist/src/cli/workspace-signals.js +2 -2
- package/dist/src/config/aiwg-config.js +54 -27
- package/dist/src/config/cli.js +3 -3
- package/dist/src/config/project-artifacts-health.js +2 -0
- package/dist/src/config/project-artifacts-health.mjs +123 -0
- package/dist/src/config/project-artifacts-runtime.mjs +16 -0
- package/dist/src/config/project-artifacts.js +2 -1
- package/dist/src/cost/fleet-report.js +329 -0
- package/dist/src/evidence/bundle.js +256 -0
- package/dist/src/extensions/commands/definitions.js +77 -25
- package/dist/src/extensions/deployment-registration.js +6 -4
- package/dist/src/extensions/project-local-doctor.js +10 -6
- package/dist/src/extensions/project-local-gitignore.js +8 -4
- package/dist/src/extensions/project-quickref.js +197 -10
- package/dist/src/features/catalog.js +26 -0
- package/dist/src/features/cli.js +1 -3
- package/dist/src/features/runtime.js +17 -1
- package/dist/src/issues/cli.js +91 -7
- package/dist/src/mcp/server.mjs +1 -1
- package/dist/src/ops/registry.js +2 -2
- package/dist/src/policy/authorization.js +2 -2
- package/dist/src/providers/capability-matrix.yaml +3 -3
- package/dist/src/providers/provider-definitions.js +7 -5
- package/dist/src/providers/provider-definitions.mjs +1 -1
- package/dist/src/serve/pty-bridge.js +2 -8
- package/dist/src/serve/screen-reader.js +3 -6
- package/dist/src/smiths/context-pipeline/aiwg-md.js +2 -2
- package/dist/src/smiths/context-pipeline/finalization.js +18 -5
- package/dist/src/smiths/context-pipeline/generator.js +2 -2
- package/dist/src/smiths/context-pipeline/workspace-context.js +16 -17
- package/package.json +2 -1
- package/tools/agents/deploy-agents.mjs +10 -11
- package/tools/agents/providers/base.mjs +47 -5
- package/tools/agents/providers/openclaw.mjs +5 -2
- package/tools/agents/providers/windsurf.mjs +13 -24
- package/tools/plugin/package-plugins.mjs +53 -0
- package/tools/skills/deploy-skills-codex.mjs +21 -5
|
@@ -4,7 +4,7 @@ import { createHash } from 'crypto';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import os from 'os';
|
|
6
6
|
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
7
|
-
import { PROJECT_AIWG_LOCATION_FILE, projectAiwgPath } from '../config/project-artifacts.js';
|
|
7
|
+
import { PROJECT_AIWG_LOCATION_FILE, projectAiwgPath, projectControlPath, } from '../config/project-artifacts.js';
|
|
8
8
|
const DEFAULT_MAX_BYTES = 1_048_576;
|
|
9
9
|
const DEFAULT_REPORT_LIMIT = 20;
|
|
10
10
|
export async function maybeAppendCommandLog(input) {
|
|
@@ -157,7 +157,7 @@ async function findProjectRoot(startDir) {
|
|
|
157
157
|
while (current !== path.dirname(current)) {
|
|
158
158
|
if (existsSync(path.join(current, '.aiwg')) ||
|
|
159
159
|
existsSync(path.join(current, PROJECT_AIWG_LOCATION_FILE)) ||
|
|
160
|
-
existsSync(
|
|
160
|
+
existsSync(projectControlPath(current, 'aiwg.config'))) {
|
|
161
161
|
return current;
|
|
162
162
|
}
|
|
163
163
|
current = path.dirname(current);
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { getProjectDir } from '../../config/aiwg-config.js';
|
|
2
2
|
import { moveProjectArtifacts } from '../../artifacts/move.js';
|
|
3
|
+
import { repairProjectArtifacts } from '../../artifacts/repair.js';
|
|
4
|
+
import { resolveProjectAiwgDir } from '../../config/project-artifacts.js';
|
|
3
5
|
function usage() {
|
|
4
6
|
return [
|
|
5
7
|
'aiwg artifacts — Manage the project AIWG artifact root',
|
|
6
8
|
'',
|
|
7
9
|
'Usage:',
|
|
10
|
+
' aiwg artifacts path [--json]',
|
|
8
11
|
' aiwg artifacts move --to <path> [--from <path>] [--dry-run] [--no-reindex] [--no-sync]',
|
|
9
12
|
' aiwg artifacts attach --to <existing-path> [--dry-run] [--no-reindex] [--no-sync]',
|
|
13
|
+
' aiwg artifacts repair --dry-run',
|
|
14
|
+
' aiwg artifacts repair --apply',
|
|
10
15
|
'',
|
|
11
16
|
'Notes:',
|
|
12
17
|
' move relocates a local artifact root; attach adopts an existing populated root.',
|
|
@@ -32,9 +37,53 @@ export const artifactsHandler = {
|
|
|
32
37
|
if (action === 'help' || ctx.args.includes('--help') || ctx.args.includes('-h')) {
|
|
33
38
|
return { exitCode: 0, message: usage() };
|
|
34
39
|
}
|
|
35
|
-
if (action !== 'move' && action !== 'attach') {
|
|
40
|
+
if (action !== 'path' && action !== 'move' && action !== 'attach' && action !== 'repair') {
|
|
36
41
|
return { exitCode: 1, message: `Unknown artifacts action: ${action}\n\n${usage()}` };
|
|
37
42
|
}
|
|
43
|
+
if (action === 'path') {
|
|
44
|
+
const projectDir = getProjectDir(ctx, ctx.args);
|
|
45
|
+
const artifactRoot = resolveProjectAiwgDir(projectDir);
|
|
46
|
+
if (ctx.args.includes('--json')) {
|
|
47
|
+
return {
|
|
48
|
+
exitCode: 0,
|
|
49
|
+
message: JSON.stringify({
|
|
50
|
+
schema: 'aiwg.artifacts.path.v1',
|
|
51
|
+
project_root: projectDir,
|
|
52
|
+
artifact_root: artifactRoot,
|
|
53
|
+
}, null, 2),
|
|
54
|
+
rawOutput: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return { exitCode: 0, message: artifactRoot, rawOutput: true };
|
|
58
|
+
}
|
|
59
|
+
if (action === 'repair') {
|
|
60
|
+
try {
|
|
61
|
+
const applied = ctx.args.includes('--apply');
|
|
62
|
+
const result = await repairProjectArtifacts({
|
|
63
|
+
projectDir: getProjectDir(ctx, ctx.args),
|
|
64
|
+
apply: applied,
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
exitCode: 0,
|
|
68
|
+
message: [
|
|
69
|
+
`${applied ? 'Repaired' : 'Artifact repair dry run for'} ${result.before.classification}`,
|
|
70
|
+
` Local control plane: ${result.before.local_control_root}`,
|
|
71
|
+
` External corpus: ${result.before.artifact_root}`,
|
|
72
|
+
` Copy locally: ${result.copied.length ? result.copied.join(', ') : 'none'}`,
|
|
73
|
+
` Remove local identical corpus copies: ${result.removed.length ? result.removed.join(', ') : 'none'}`,
|
|
74
|
+
` Result: ${result.after.classification}`,
|
|
75
|
+
applied ? '' : 'No files changed. Re-run with --apply after reviewing this plan.',
|
|
76
|
+
].filter(Boolean).join('\n'),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
exitCode: 1,
|
|
82
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
83
|
+
message: `Artifact repair failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
38
87
|
const to = valueAfter(ctx.args, '--to');
|
|
39
88
|
if (!to) {
|
|
40
89
|
return { exitCode: 1, message: `Error: --to <path> is required.\n\n${usage()}` };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** OpenRouter fleet cost-report CLI handler. @issue #1187 */
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { FleetConfigMissingError, formatFleetSpendReport, generateFleetSpendReport, } from '../../cost/fleet-report.js';
|
|
5
|
+
function option(args, name) {
|
|
6
|
+
const index = args.indexOf(name);
|
|
7
|
+
const value = index >= 0 ? args[index + 1] : undefined;
|
|
8
|
+
return value && !value.startsWith('-') ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function usage() {
|
|
11
|
+
return [
|
|
12
|
+
'Usage: aiwg cost-report (--fleet | --key <key_ref>) [--source openrouter] [--config <fleet.yaml>] [--json]',
|
|
13
|
+
'',
|
|
14
|
+
'Reads bot-to-key references from ~/.config/aiwg/fleet.yaml and credentials from',
|
|
15
|
+
'~/.config/aiwg/keys/<key_ref> or AIWG_OPENROUTER_KEY_<KEY_REF>.',
|
|
16
|
+
'',
|
|
17
|
+
'AIWG observes and correlates spend; OpenRouter enforces all key limits and caps.',
|
|
18
|
+
].join('\n');
|
|
19
|
+
}
|
|
20
|
+
export const costReportHandler = {
|
|
21
|
+
id: 'cost-report',
|
|
22
|
+
name: 'Cost Report',
|
|
23
|
+
description: 'Observe OpenRouter fleet spend and correlate it with local AIWG activity',
|
|
24
|
+
category: 'utility',
|
|
25
|
+
aliases: [],
|
|
26
|
+
async execute(ctx) {
|
|
27
|
+
if (ctx.args.includes('--help') || ctx.args.includes('-h'))
|
|
28
|
+
return { exitCode: 0, message: usage() };
|
|
29
|
+
const keyRef = option(ctx.args, '--key');
|
|
30
|
+
if (ctx.args.includes('--fleet') && keyRef)
|
|
31
|
+
return { exitCode: 2, message: 'Choose either --fleet or --key, not both.' };
|
|
32
|
+
if (!ctx.args.includes('--fleet') && !keyRef) {
|
|
33
|
+
return { exitCode: 2, message: `Select --fleet or --key <key_ref>.\n\n${usage()}` };
|
|
34
|
+
}
|
|
35
|
+
const source = option(ctx.args, '--source') ?? 'openrouter';
|
|
36
|
+
if (source !== 'openrouter')
|
|
37
|
+
return { exitCode: 2, message: `Unsupported source '${source}'. Only openrouter is supported.` };
|
|
38
|
+
const configuredPath = option(ctx.args, '--config');
|
|
39
|
+
const configPath = configuredPath
|
|
40
|
+
? path.resolve(ctx.cwd, configuredPath)
|
|
41
|
+
: path.join(os.homedir(), '.config', 'aiwg', 'fleet.yaml');
|
|
42
|
+
try {
|
|
43
|
+
const capValue = option(ctx.args, '--monthly-cap');
|
|
44
|
+
const cap = capValue === undefined ? 0 : Number(capValue);
|
|
45
|
+
if (!Number.isFinite(cap) || cap < 0)
|
|
46
|
+
return { exitCode: 2, message: '--monthly-cap must be a non-negative number.' };
|
|
47
|
+
const report = await generateFleetSpendReport({
|
|
48
|
+
cwd: ctx.cwd,
|
|
49
|
+
configPath,
|
|
50
|
+
signal: ctx.signal,
|
|
51
|
+
...(keyRef ? {
|
|
52
|
+
fleet: [{
|
|
53
|
+
bot: option(ctx.args, '--bot') ?? keyRef,
|
|
54
|
+
machine: option(ctx.args, '--machine') ?? os.hostname(),
|
|
55
|
+
key_ref: keyRef,
|
|
56
|
+
monthly_cap: cap,
|
|
57
|
+
}],
|
|
58
|
+
} : {}),
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
exitCode: report.bots.some(bot => bot.error) ? 1 : 0,
|
|
62
|
+
message: ctx.args.includes('--json') ? JSON.stringify(report, null, 2) : formatFleetSpendReport(report),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const message = error instanceof FleetConfigMissingError ? error.message : `Fleet cost report failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
67
|
+
return { exitCode: 1, message };
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=cost-report.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Evidence bundle export and verification CLI. @issue #2039 */
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createEvidenceBundle, verifyEvidenceBundle } from '../../evidence/bundle.js';
|
|
4
|
+
function values(args, flag) {
|
|
5
|
+
const result = [];
|
|
6
|
+
for (let index = 0; index < args.length; index++)
|
|
7
|
+
if (args[index] === flag && args[index + 1])
|
|
8
|
+
result.push(args[++index]);
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
function value(args, flag) { return values(args, flag)[0]; }
|
|
12
|
+
function versions(items) {
|
|
13
|
+
return Object.fromEntries(items.map(item => {
|
|
14
|
+
const index = item.indexOf('=');
|
|
15
|
+
if (index < 1 || index === item.length - 1)
|
|
16
|
+
throw new Error(`version '${item}' must use name=value`);
|
|
17
|
+
return [item.slice(0, index), item.slice(index + 1)];
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
function usage() {
|
|
21
|
+
return [
|
|
22
|
+
'Usage:',
|
|
23
|
+
' aiwg evidence export --output <dir> [--activity-export <json>] [--report <file>] [--source <file>]',
|
|
24
|
+
' [--eval-config <file>] [--provenance <file>] [--model-version name=value] [--tool-version name=value]',
|
|
25
|
+
' [--check-only --not-run <reason>] [--json]',
|
|
26
|
+
' aiwg evidence verify <bundle> [--expected-root <sha256>] [--json]',
|
|
27
|
+
].join('\n');
|
|
28
|
+
}
|
|
29
|
+
export const evidenceHandler = {
|
|
30
|
+
id: 'evidence', name: 'Evidence', description: 'Export and verify portable evaluation evidence bundles', category: 'utility', aliases: [],
|
|
31
|
+
async execute(ctx) {
|
|
32
|
+
const [subcommand] = ctx.args;
|
|
33
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h')
|
|
34
|
+
return { exitCode: 0, message: usage() };
|
|
35
|
+
try {
|
|
36
|
+
if (subcommand === 'verify') {
|
|
37
|
+
const bundle = ctx.args[1];
|
|
38
|
+
if (!bundle || bundle.startsWith('-'))
|
|
39
|
+
return { exitCode: 2, message: usage() };
|
|
40
|
+
const expectedRoot = value(ctx.args, '--expected-root');
|
|
41
|
+
if (expectedRoot && !/^[0-9a-f]{64}$/i.test(expectedRoot))
|
|
42
|
+
return { exitCode: 2, message: '--expected-root must be a SHA-256 hex digest' };
|
|
43
|
+
const result = await verifyEvidenceBundle(path.resolve(ctx.cwd, bundle), expectedRoot?.toLowerCase());
|
|
44
|
+
return { exitCode: result.valid ? 0 : 1, message: ctx.args.includes('--json') ? JSON.stringify(result, null, 2) : [
|
|
45
|
+
`Evidence bundle: ${result.valid ? 'VALID' : 'INVALID'} (${result.status})`,
|
|
46
|
+
...result.errors.map(error => `ERROR: ${error}`), ...result.warnings.map(warning => `WARN: ${warning}`),
|
|
47
|
+
].join('\n') };
|
|
48
|
+
}
|
|
49
|
+
if (subcommand !== 'export')
|
|
50
|
+
return { exitCode: 2, message: usage() };
|
|
51
|
+
const output = value(ctx.args, '--output');
|
|
52
|
+
if (!output)
|
|
53
|
+
return { exitCode: 2, message: '--output is required.\n\n' + usage() };
|
|
54
|
+
const checkOnly = ctx.args.includes('--check-only');
|
|
55
|
+
const notRunReason = value(ctx.args, '--not-run');
|
|
56
|
+
if (checkOnly !== Boolean(notRunReason))
|
|
57
|
+
return { exitCode: 2, message: '--check-only and --not-run <reason> must be used together' };
|
|
58
|
+
const inputs = [
|
|
59
|
+
...values(ctx.args, '--activity-export').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'activity-export' })),
|
|
60
|
+
...values(ctx.args, '--report').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'report' })),
|
|
61
|
+
...values(ctx.args, '--source').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'source' })),
|
|
62
|
+
...values(ctx.args, '--eval-config').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'eval-config' })),
|
|
63
|
+
...values(ctx.args, '--provenance').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'provenance' })),
|
|
64
|
+
];
|
|
65
|
+
const manifest = await createEvidenceBundle({
|
|
66
|
+
output: path.resolve(ctx.cwd, output), inputs,
|
|
67
|
+
modelVersions: versions(values(ctx.args, '--model-version')),
|
|
68
|
+
toolVersions: { aiwg: process.env.npm_package_version ?? 'unknown', node: process.version, ...versions(values(ctx.args, '--tool-version')) },
|
|
69
|
+
checkOnly, notRunReason,
|
|
70
|
+
});
|
|
71
|
+
return { exitCode: 0, message: ctx.args.includes('--json') ? JSON.stringify(manifest, null, 2) : `Evidence bundle ${manifest.status}: ${path.resolve(ctx.cwd, output)}\nVerifier root: ${manifest.verifier.root}` };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return { exitCode: 1, message: `Evidence command failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=evidence.js.map
|
|
@@ -95,6 +95,15 @@ function displayHelp() {
|
|
|
95
95
|
]);
|
|
96
96
|
helpGroup('VALIDATION', [
|
|
97
97
|
['validate-metadata [path]', 'Validate AIWG component metadata (defaults to agentic/code)'],
|
|
98
|
+
['context-firewall [scan]', 'Audit provider context, trust, drift, poisoning signals, and budget'],
|
|
99
|
+
['context-firewall baseline', 'Plan or explicitly write the reviewed context baseline'],
|
|
100
|
+
]);
|
|
101
|
+
helpGroup('METRICS', [
|
|
102
|
+
['cost-report --fleet', 'Observe OpenRouter per-bot MTD spend and correlate local activity'],
|
|
103
|
+
]);
|
|
104
|
+
helpGroup('EVIDENCE', [
|
|
105
|
+
['evidence export --output <dir>', 'Package portable activity, report, source, eval, and provenance evidence'],
|
|
106
|
+
['evidence verify <bundle>', 'Verify every member hash and the bundle checkpoint'],
|
|
98
107
|
]);
|
|
99
108
|
helpGroup('SCAFFOLDING', [
|
|
100
109
|
['new-bundle <name>', 'Create project-local bundle (--type extension|addon|framework|plugin|provider, --starter skill|rule|agent|minimal, --dry-run)'],
|
|
@@ -16,7 +16,7 @@ import { versionHandler } from './version.js';
|
|
|
16
16
|
import { authHandler } from './auth.js';
|
|
17
17
|
import { useHandler } from './use.js';
|
|
18
18
|
import { statusHandler, wizardHandler, migrateWorkspaceHandler, rollbackWorkspaceHandler, workspaceHandlers, } from './workspace.js';
|
|
19
|
-
import { prefillCardsHandler, contributeStartHandler, validateMetadataHandler, doctorHandler, updateHandler, utilityHandlers, } from './utilities.js';
|
|
19
|
+
import { prefillCardsHandler, contributeStartHandler, validateMetadataHandler, doctorHandler, contextFirewallHandler, updateHandler, utilityHandlers, } from './utilities.js';
|
|
20
20
|
import { skillLintHandler } from './skill-lint.js';
|
|
21
21
|
import { addAgentHandler, addCommandHandler, addSkillHandler, addBehaviorHandler, addTemplateHandler, scaffoldAddonHandler, scaffoldExtensionHandler, scaffoldFrameworkHandler, scaffoldingHandlers, } from './scaffolding.js';
|
|
22
22
|
import { behaviorHandler, daemonInitHandler, daemonHandlers, } from './daemon.js';
|
|
@@ -59,12 +59,14 @@ import { skillUsageHandler } from './skill-usage.js';
|
|
|
59
59
|
import { modelsHandler } from './models.js';
|
|
60
60
|
import { versionsHandler } from './resource-versions.js';
|
|
61
61
|
import { jobHandler } from './job.js';
|
|
62
|
+
import { costReportHandler } from './cost-report.js';
|
|
63
|
+
import { evidenceHandler } from './evidence.js';
|
|
62
64
|
// Re-export individual handlers
|
|
63
65
|
export {
|
|
64
66
|
// Maintenance
|
|
65
|
-
helpHandler, versionHandler, authHandler, doctorHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
|
|
67
|
+
helpHandler, versionHandler, authHandler, doctorHandler, contextFirewallHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
|
|
66
68
|
// Framework management
|
|
67
|
-
useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler,
|
|
69
|
+
useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler,
|
|
68
70
|
// Project
|
|
69
71
|
newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
|
|
70
72
|
// Workspace
|
|
@@ -118,6 +120,7 @@ export const allHandlers = [
|
|
|
118
120
|
versionHandler,
|
|
119
121
|
authHandler,
|
|
120
122
|
doctorHandler,
|
|
123
|
+
contextFirewallHandler,
|
|
121
124
|
updateHandler,
|
|
122
125
|
refreshHandler,
|
|
123
126
|
regenerateHandler,
|
|
@@ -143,6 +146,8 @@ export const allHandlers = [
|
|
|
143
146
|
issueAuditHandler,
|
|
144
147
|
runHandler,
|
|
145
148
|
jobHandler,
|
|
149
|
+
costReportHandler,
|
|
150
|
+
evidenceHandler,
|
|
146
151
|
// Workspace management
|
|
147
152
|
...workspaceHandlers,
|
|
148
153
|
// Subcommand handlers (MCP, catalog, index, skills)
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import path from 'path';
|
|
20
20
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
21
21
|
import { randomUUID } from 'crypto';
|
|
22
|
+
import { pathToFileURL } from 'node:url';
|
|
22
23
|
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
23
24
|
const DEFAULT_PORT = 8200;
|
|
24
25
|
const DEFAULT_BIND = '127.0.0.1';
|
|
@@ -131,7 +132,7 @@ export const localExecutorServeHandler = {
|
|
|
131
132
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
133
|
let DaemonSupervisor;
|
|
133
134
|
try {
|
|
134
|
-
const shimMod = await
|
|
135
|
+
const shimMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'executor-shim.mjs')).href);
|
|
135
136
|
ExecutorShim = shimMod.ExecutorShim;
|
|
136
137
|
startExecutorServer = shimMod.startExecutorServer;
|
|
137
138
|
}
|
|
@@ -142,7 +143,7 @@ export const localExecutorServeHandler = {
|
|
|
142
143
|
};
|
|
143
144
|
}
|
|
144
145
|
try {
|
|
145
|
-
const dsMod = await
|
|
146
|
+
const dsMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'daemon-supervisor.mjs')).href);
|
|
146
147
|
DaemonSupervisor = dsMod.DaemonSupervisor;
|
|
147
148
|
}
|
|
148
149
|
catch (err) {
|
|
@@ -155,7 +156,7 @@ export const localExecutorServeHandler = {
|
|
|
155
156
|
// requires it. Try loading from ralph-external/orchestrator.mjs.
|
|
156
157
|
let agentSupervisorInstance = null;
|
|
157
158
|
try {
|
|
158
|
-
const orchMod = await
|
|
159
|
+
const orchMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'orchestrator.mjs')).href);
|
|
159
160
|
const OrchClass = orchMod.Orchestrator ?? orchMod.default;
|
|
160
161
|
if (OrchClass) {
|
|
161
162
|
const orch = new OrchClass({ maxConcurrent: opts.maxConcurrency });
|
|
@@ -149,7 +149,7 @@ export function collectModelDeployArgs(args) {
|
|
|
149
149
|
export const refreshHandler = {
|
|
150
150
|
id: 'refresh',
|
|
151
151
|
name: 'Refresh',
|
|
152
|
-
description: 'Refresh AIWG to latest version and re-deploy
|
|
152
|
+
description: 'Refresh AIWG to latest version and re-deploy installed frameworks',
|
|
153
153
|
category: 'maintenance',
|
|
154
154
|
aliases: ['--refresh', 'sync', '--sync'],
|
|
155
155
|
async execute(ctx) {
|
|
@@ -241,13 +241,24 @@ export const refreshHandler = {
|
|
|
241
241
|
if (!quiet)
|
|
242
242
|
ui.dim(' Skipping package update (--skip-update)');
|
|
243
243
|
}
|
|
244
|
-
// Step 4: Re-deploy frameworks
|
|
245
|
-
|
|
244
|
+
// Step 4: Re-deploy frameworks. Both the default form and --all mean
|
|
245
|
+
// "all installed", never the `aiwg use all` expansion meta-target. This
|
|
246
|
+
// preserves the operator's selected footprint and removal symmetry.
|
|
247
|
+
const refreshConfig = await readAiwgConfig(ctx.cwd);
|
|
248
|
+
const installedFrameworks = Object.keys(refreshConfig?.installed ?? {});
|
|
249
|
+
const requestedFrameworks = frameworksArg
|
|
250
|
+
? frameworksArg.split(',').map(item => item.trim()).filter(Boolean)
|
|
251
|
+
: [];
|
|
252
|
+
const frameworks = !frameworksArg || requestedFrameworks.includes('all')
|
|
253
|
+
? installedFrameworks
|
|
254
|
+
: requestedFrameworks;
|
|
246
255
|
if (!quiet)
|
|
247
256
|
ui.info(dryRun ? 'Would re-deploy frameworks...' : 'Re-deploying frameworks...');
|
|
248
257
|
if (!dryRun) {
|
|
249
|
-
|
|
250
|
-
|
|
258
|
+
if (frameworks.length === 0 && !quiet) {
|
|
259
|
+
ui.dim(' No installed frameworks or addons to re-deploy');
|
|
260
|
+
}
|
|
261
|
+
for (const fw of frameworks) {
|
|
251
262
|
const providerArgs = ['--provider', detectedProvider, ...modelDeployArgs];
|
|
252
263
|
const useResult = await runner.run('tools/cli/deploy.mjs', [fw, ...providerArgs], { capture: quiet });
|
|
253
264
|
if (useResult.exitCode === 0) {
|
|
@@ -261,9 +272,10 @@ export const refreshHandler = {
|
|
|
261
272
|
}
|
|
262
273
|
}
|
|
263
274
|
else {
|
|
264
|
-
const targets = frameworks || ['all installed frameworks'];
|
|
265
275
|
if (!quiet) {
|
|
266
|
-
|
|
276
|
+
if (frameworks.length === 0)
|
|
277
|
+
ui.dim(' No installed frameworks or addons');
|
|
278
|
+
for (const fw of frameworks) {
|
|
267
279
|
ui.dim(` Would re-deploy: ${fw}`);
|
|
268
280
|
}
|
|
269
281
|
}
|
|
@@ -419,7 +431,7 @@ export const refreshHandler = {
|
|
|
419
431
|
const output = JSON.stringify({
|
|
420
432
|
status: dryRun ? 'dry-run' : 'refreshed',
|
|
421
433
|
provider: detectedProvider,
|
|
422
|
-
frameworks
|
|
434
|
+
frameworks,
|
|
423
435
|
skipUpdate,
|
|
424
436
|
channel: channel || undefined,
|
|
425
437
|
staleAgentRemovals,
|
|
@@ -25,7 +25,8 @@ import * as ui from '../ui.js';
|
|
|
25
25
|
import { generate as generateContextFiles, discoverDeployedArtifacts, shouldEmitContextFiles, buildNormalizedAiwgMd, writeNormalizedAiwgMd, injectLegacyContext, migrateWorkspaceContext, extractExistingProjectContext, } from '../../smiths/context-pipeline/index.js';
|
|
26
26
|
import { resolveActiveProvider } from '../provider-resolution.js';
|
|
27
27
|
import { getProviderContextDiscoveryPathStrings } from '../../providers/provider-definitions.js';
|
|
28
|
-
import {
|
|
28
|
+
import { projectControlPath } from '../../config/project-artifacts.js';
|
|
29
|
+
import { selectRegenerateBranch } from '../regenerate-selector.js';
|
|
29
30
|
async function handleRegenerate(args, cwd) {
|
|
30
31
|
if (args.includes('--help') || args.includes('-h')) {
|
|
31
32
|
console.log(`
|
|
@@ -40,7 +41,8 @@ async function handleRegenerate(args, cwd) {
|
|
|
40
41
|
or commands — use 'aiwg refresh' for that.
|
|
41
42
|
|
|
42
43
|
Options:
|
|
43
|
-
|
|
44
|
+
(no branch flag) Intelligently select workspace refresh or adoption preview
|
|
45
|
+
--workspace Explicit canonical WORKSPACE.md → AIWG.md graph
|
|
44
46
|
--existing-project Transactionally extract an established project into WORKSPACE.md
|
|
45
47
|
--legacy, --full-inject Legacy inline compatibility branch
|
|
46
48
|
--apply Apply --existing-project after its mandatory preflight
|
|
@@ -57,6 +59,7 @@ async function handleRegenerate(args, cwd) {
|
|
|
57
59
|
aiwg regenerate --workspace
|
|
58
60
|
aiwg regenerate --existing-project --dry-run
|
|
59
61
|
aiwg regenerate --existing-project --apply
|
|
62
|
+
aiwg regenerate --apply
|
|
60
63
|
aiwg regenerate --full-inject
|
|
61
64
|
aiwg regenerate --dry-run
|
|
62
65
|
aiwg regenerate --provider codex
|
|
@@ -69,9 +72,9 @@ async function handleRegenerate(args, cwd) {
|
|
|
69
72
|
const skipAiwgMd = args.includes('--no-aiwg-md');
|
|
70
73
|
const skipAgentsMd = args.includes('--no-agents-md');
|
|
71
74
|
const skipWorkspaceMd = args.includes('--no-workspace-md');
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
+
const requestedLegacy = args.includes('--legacy') || args.includes('--full-inject');
|
|
76
|
+
const requestedWorkspace = args.includes('--workspace');
|
|
77
|
+
const requestedExistingProject = args.includes('--existing-project');
|
|
75
78
|
const apply = args.includes('--apply');
|
|
76
79
|
const valueFlags = new Set(['--provider']);
|
|
77
80
|
const booleanFlags = new Set([
|
|
@@ -95,23 +98,27 @@ async function handleRegenerate(args, cwd) {
|
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
|
-
const selectedBranches = Number(
|
|
101
|
+
const selectedBranches = Number(requestedLegacy) + Number(requestedWorkspace) + Number(requestedExistingProject);
|
|
99
102
|
if (selectedBranches > 1)
|
|
100
103
|
throw new AiwgError({
|
|
101
104
|
code: 'ERR_USAGE_CONFLICTING_FLAGS',
|
|
102
105
|
message: 'Choose exactly one regenerate branch: --workspace, --existing-project, or --full-inject.',
|
|
103
106
|
exitCode: EXIT_CODES.USAGE,
|
|
104
107
|
});
|
|
105
|
-
if (
|
|
108
|
+
if (dryRun && apply)
|
|
106
109
|
throw new AiwgError({
|
|
107
110
|
code: 'ERR_USAGE_CONFLICTING_FLAGS',
|
|
108
|
-
message: '
|
|
111
|
+
message: 'Choose either --dry-run or --apply.',
|
|
109
112
|
exitCode: EXIT_CODES.USAGE,
|
|
110
113
|
});
|
|
111
|
-
|
|
114
|
+
const selection = await selectRegenerateBranch(cwd, args);
|
|
115
|
+
const legacy = selection.branch === 'legacy';
|
|
116
|
+
const existingProject = selection.branch === 'existing-project';
|
|
117
|
+
if (apply && !existingProject)
|
|
112
118
|
throw new AiwgError({
|
|
113
119
|
code: 'ERR_USAGE_CONFLICTING_FLAGS',
|
|
114
|
-
message: '
|
|
120
|
+
message: '--apply is only valid when the existing-project branch is selected.',
|
|
121
|
+
hint: 'Use `aiwg regenerate --existing-project --apply`, or run without --apply to inspect the selected branch.',
|
|
115
122
|
exitCode: EXIT_CODES.USAGE,
|
|
116
123
|
});
|
|
117
124
|
if (existingProject && (force || skipAiwgMd || skipAgentsMd || skipWorkspaceMd))
|
|
@@ -140,6 +147,9 @@ async function handleRegenerate(args, cwd) {
|
|
|
140
147
|
console.log(` Provider: ${provider}`);
|
|
141
148
|
console.log(` Target: ${target}`);
|
|
142
149
|
console.log(` Branch: ${legacy ? 'legacy full injection' : existingProject ? 'canonical existing-project extraction' : 'canonical workspace graph'}`);
|
|
150
|
+
console.log(` Selected: ${selection.explicit ? 'explicit' : 'inferred'} — ${selection.reason}`);
|
|
151
|
+
if (selection.evidence.length > 0)
|
|
152
|
+
console.log(` Evidence: ${selection.evidence.join(', ')}`);
|
|
143
153
|
if (existingProject) {
|
|
144
154
|
const preflight = await migrateWorkspaceContext(target, {
|
|
145
155
|
dryRun: true,
|
|
@@ -188,7 +198,7 @@ async function handleRegenerate(args, cwd) {
|
|
|
188
198
|
if (legacy) {
|
|
189
199
|
if (skipWorkspaceMd)
|
|
190
200
|
console.log(' Note: --no-workspace-md is implicit in legacy mode.');
|
|
191
|
-
const normalizedPath =
|
|
201
|
+
const normalizedPath = projectControlPath(target, 'AIWG.md');
|
|
192
202
|
let existing = '';
|
|
193
203
|
try {
|
|
194
204
|
existing = await fs.readFile(normalizedPath, 'utf8');
|
|
@@ -236,7 +246,7 @@ async function handleRegenerate(args, cwd) {
|
|
|
236
246
|
console.log(` Would regenerate:`);
|
|
237
247
|
if (!skipWorkspaceMd)
|
|
238
248
|
console.log(` - ${path.join(target, 'WORKSPACE.md')} (managed graph; operator section preserved)`);
|
|
239
|
-
console.log(` - ${
|
|
249
|
+
console.log(` - ${projectControlPath(target, 'AIWG.md')}`);
|
|
240
250
|
if (!skipAiwgMd)
|
|
241
251
|
console.log(` - ${aiwgMd}`);
|
|
242
252
|
if (provider === 'claude') {
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { existsSync, readFileSync } from 'fs';
|
|
13
|
-
import { spawnSync } from 'child_process';
|
|
14
13
|
import { createPtyWsHandler, registry as ptyRegistry } from '../../serve/pty-bridge.js';
|
|
15
14
|
import { telemetryStore, createEvent } from '../../serve/telemetry.js';
|
|
16
15
|
import { sandboxRegistry, normalizeSandboxEvent, } from '../../serve/sandbox-registry.js';
|
|
@@ -21,6 +20,7 @@ import { executorRegistry, validateRegisterPayload, validateDispatchPayload, val
|
|
|
21
20
|
import { handleWebhook, IdempotencyCache, PushSecretRegistry, } from '../../a2a/webhook.js';
|
|
22
21
|
import { AiwgError, EXIT_CODES } from '../errors.js';
|
|
23
22
|
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
23
|
+
import { loadFeaturePackage } from '../../features/runtime.js';
|
|
24
24
|
// A2A push-notification state — module-scoped so the test harness can
|
|
25
25
|
// monkey-patch them in if needed. One process serves one set of secrets.
|
|
26
26
|
const pushSecretRegistry = new PushSecretRegistry();
|
|
@@ -190,11 +190,10 @@ async function setupWebSockets(httpServer, readOnly) {
|
|
|
190
190
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
191
191
|
let wsMod;
|
|
192
192
|
try {
|
|
193
|
-
|
|
194
|
-
wsMod = await import('ws');
|
|
193
|
+
wsMod = await loadFeaturePackage('ws');
|
|
195
194
|
}
|
|
196
195
|
catch {
|
|
197
|
-
console.warn('[serve]
|
|
196
|
+
console.warn('[serve] WebSocket routes disabled — run `aiwg features install webserver` to enable them.');
|
|
198
197
|
return;
|
|
199
198
|
}
|
|
200
199
|
// ws ships as CJS; ESM import may wrap in .default
|
|
@@ -510,40 +509,19 @@ export async function startServer(opts) {
|
|
|
510
509
|
// import path raises ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING (#1277).
|
|
511
510
|
// hono is an optionalDependency; tsc may not find its types under
|
|
512
511
|
// `npm ci --omit=optional` (e.g. metadata-validation workflow). The
|
|
513
|
-
// try/catch
|
|
512
|
+
// try/catch below emits the managed feature-install route at runtime.
|
|
514
513
|
// @ts-ignore — optional dep; may not be installed at typecheck time
|
|
515
|
-
honoMod = await
|
|
514
|
+
honoMod = await loadFeaturePackage('hono');
|
|
516
515
|
// @ts-ignore — optional dep; may not be installed at typecheck time
|
|
517
|
-
nodeMod = await
|
|
516
|
+
nodeMod = await loadFeaturePackage('@hono/node-server');
|
|
518
517
|
}
|
|
519
518
|
catch {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
message: 'Failed to install serve dependencies (hono, @hono/node-server, ws)',
|
|
527
|
-
hint: 'Install manually: npm install hono @hono/node-server ws',
|
|
528
|
-
exitCode: EXIT_CODES.GENERAL,
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
// Retry imports after install
|
|
532
|
-
try {
|
|
533
|
-
// @ts-ignore — optional dep; may not be installed at typecheck time
|
|
534
|
-
honoMod = await import('hono');
|
|
535
|
-
// @ts-ignore — optional dep; may not be installed at typecheck time
|
|
536
|
-
nodeMod = await import('@hono/node-server');
|
|
537
|
-
}
|
|
538
|
-
catch (err) {
|
|
539
|
-
throw new AiwgError({
|
|
540
|
-
code: 'ERR_SERVE_DEPS_LOAD_FAILED',
|
|
541
|
-
message: 'Serve dependencies installed but could not be loaded',
|
|
542
|
-
hint: 'Try: npm install hono @hono/node-server ws',
|
|
543
|
-
exitCode: EXIT_CODES.GENERAL,
|
|
544
|
-
cause: err,
|
|
545
|
-
});
|
|
546
|
-
}
|
|
519
|
+
throw new AiwgError({
|
|
520
|
+
code: 'ERR_SERVE_DEPS_MISSING',
|
|
521
|
+
message: 'The optional webserver feature is not available',
|
|
522
|
+
hint: 'Run `aiwg features install webserver`, then retry `aiwg serve`.',
|
|
523
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
524
|
+
});
|
|
547
525
|
}
|
|
548
526
|
const { Hono } = honoMod;
|
|
549
527
|
const { serve } = nodeMod;
|
|
@@ -1692,7 +1670,8 @@ export async function startServer(opts) {
|
|
|
1692
1670
|
const webDistDir = path.join(opts.frameworkRoot, 'apps', 'web', 'dist');
|
|
1693
1671
|
if (existsSync(webDistDir)) {
|
|
1694
1672
|
try {
|
|
1695
|
-
|
|
1673
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1674
|
+
const { serveStatic } = await loadFeaturePackage('@hono/node-server/serve-static');
|
|
1696
1675
|
app.use('/*', serveStatic({ root: webDistDir }));
|
|
1697
1676
|
}
|
|
1698
1677
|
catch {
|
|
@@ -1788,7 +1767,7 @@ export const serveHandler = {
|
|
|
1788
1767
|
if (open) {
|
|
1789
1768
|
try {
|
|
1790
1769
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1791
|
-
const openMod = await (
|
|
1770
|
+
const openMod = await loadFeaturePackage('open');
|
|
1792
1771
|
const openBrowser = openMod.default ?? openMod;
|
|
1793
1772
|
await openBrowser(url);
|
|
1794
1773
|
}
|
|
@@ -1203,15 +1203,16 @@ function projectRootCandidate(start) {
|
|
|
1203
1203
|
catch {
|
|
1204
1204
|
current = resolve(start);
|
|
1205
1205
|
}
|
|
1206
|
-
let gitRoot = null;
|
|
1207
1206
|
while (true) {
|
|
1208
1207
|
if (existsSync(resolve(current, '.aiwg', 'aiwg.config')))
|
|
1209
1208
|
return current;
|
|
1210
|
-
|
|
1211
|
-
|
|
1209
|
+
// A repository root is the project boundary. Do not let an unrelated
|
|
1210
|
+
// ancestor workspace configuration capture session catalog reads.
|
|
1211
|
+
if (existsSync(resolve(current, '.git')))
|
|
1212
|
+
return current;
|
|
1212
1213
|
const parent = dirname(current);
|
|
1213
1214
|
if (parent === current)
|
|
1214
|
-
return
|
|
1215
|
+
return null;
|
|
1215
1216
|
current = parent;
|
|
1216
1217
|
}
|
|
1217
1218
|
}
|