forgeos 0.1.0-alpha.2 → 0.1.0-alpha.4
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/AGENTS.md +38 -3
- package/CHANGELOG.md +29 -0
- package/README.md +25 -10
- package/package.json +8 -5
- package/src/forge/_generated/actionSubscriptions.json +2 -2
- package/src/forge/_generated/actionSubscriptions.ts +3 -3
- package/src/forge/_generated/agentAdapterManifest.json +2 -2
- package/src/forge/_generated/agentAdapterManifest.ts +3 -3
- package/src/forge/_generated/agentContract.json +2 -2
- package/src/forge/_generated/agentContract.ts +183 -50
- package/src/forge/_generated/agentQuickstart.md +3 -1
- package/src/forge/_generated/agentTools.json +2 -0
- package/src/forge/_generated/agentTools.md +16 -0
- package/src/forge/_generated/agentTools.ts +12 -0
- package/src/forge/_generated/aiContext.ts +67 -1
- package/src/forge/_generated/aiModels.json +2 -2
- package/src/forge/_generated/aiModels.ts +17 -1
- package/src/forge/_generated/aiProviders.json +1 -1
- package/src/forge/_generated/aiProviders.ts +1 -1
- package/src/forge/_generated/aiRegistry.json +2 -2
- package/src/forge/_generated/aiRegistry.ts +7 -5
- package/src/forge/_generated/api.json +2 -2
- package/src/forge/_generated/api.ts +1 -1
- package/src/forge/_generated/appGraph.json +2 -2
- package/src/forge/_generated/appGraph.ts +512 -260
- package/src/forge/_generated/appMap.md +21 -1
- package/src/forge/_generated/artifactManifest.json +2 -2
- package/src/forge/_generated/artifactManifest.ts +2 -2
- package/src/forge/_generated/authClaims.json +1 -1
- package/src/forge/_generated/authClaims.ts +1 -1
- package/src/forge/_generated/authConfig.json +1 -1
- package/src/forge/_generated/authConfig.ts +1 -1
- package/src/forge/_generated/authContext.ts +1 -1
- package/src/forge/_generated/authRegistry.json +1 -1
- package/src/forge/_generated/authRegistry.ts +1 -1
- package/src/forge/_generated/buildInfo.json +2 -2
- package/src/forge/_generated/buildInfo.ts +4 -4
- package/src/forge/_generated/capabilityMap.json +2 -2
- package/src/forge/_generated/capabilityMap.md +1 -1
- package/src/forge/_generated/capabilityMap.ts +2 -2
- package/src/forge/_generated/client.ts +1 -1
- package/src/forge/_generated/clientApi.ts +1 -1
- package/src/forge/_generated/clientManifest.json +2 -2
- package/src/forge/_generated/clientManifest.ts +3 -3
- package/src/forge/_generated/clientTypes.ts +1 -1
- package/src/forge/_generated/configRegistry.json +1 -1
- package/src/forge/_generated/configRegistry.ts +1 -1
- package/src/forge/_generated/dataGraph.json +2 -2
- package/src/forge/_generated/dataGraph.ts +3 -3
- package/src/forge/_generated/db.json +1 -1
- package/src/forge/_generated/db.ts +1 -1
- package/src/forge/_generated/dbSecurityManifest.json +1 -1
- package/src/forge/_generated/dbSecurityManifest.ts +1 -1
- package/src/forge/_generated/dbSessionContext.json +1 -1
- package/src/forge/_generated/dbSessionContext.ts +1 -1
- package/src/forge/_generated/deployManifest.json +2 -2
- package/src/forge/_generated/deployManifest.ts +7 -7
- package/src/forge/_generated/devManifest.json +2 -2
- package/src/forge/_generated/devManifest.ts +18 -3
- package/src/forge/_generated/envSchema.json +1 -1
- package/src/forge/_generated/envSchema.ts +1 -1
- package/src/forge/_generated/frontendGraph.json +1 -1
- package/src/forge/_generated/frontendGraph.ts +1 -1
- package/src/forge/_generated/importGuards.json +1 -1
- package/src/forge/_generated/importGuards.ts +1 -1
- package/src/forge/_generated/index.ts +2 -1
- package/src/forge/_generated/liveProductionManifest.json +1 -1
- package/src/forge/_generated/liveProductionManifest.ts +1 -1
- package/src/forge/_generated/liveProtocol.json +1 -1
- package/src/forge/_generated/liveProtocol.ts +1 -1
- package/src/forge/_generated/liveQueryRegistry.json +2 -2
- package/src/forge/_generated/liveQueryRegistry.ts +3 -3
- package/src/forge/_generated/liveTransportConfig.json +1 -1
- package/src/forge/_generated/liveTransportConfig.ts +1 -1
- package/src/forge/_generated/makeRegistry.json +2 -2
- package/src/forge/_generated/makeRegistry.ts +16 -2
- package/src/forge/_generated/makeTemplates.json +2 -2
- package/src/forge/_generated/makeTemplates.ts +6 -1
- package/src/forge/_generated/mockMap.json +1 -1
- package/src/forge/_generated/mockMap.ts +1 -1
- package/src/forge/_generated/operationPlaybooks.md +34 -14
- package/src/forge/_generated/packageGraph.json +2 -2
- package/src/forge/_generated/packageGraph.ts +8808 -4723
- package/src/forge/_generated/packageUpgradeRegistry.json +2 -2
- package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
- package/src/forge/_generated/permissionMatrix.json +2 -2
- package/src/forge/_generated/permissionMatrix.ts +3 -3
- package/src/forge/_generated/policyRegistry.json +2 -2
- package/src/forge/_generated/policyRegistry.ts +3 -3
- package/src/forge/_generated/queryRegistry.json +2 -2
- package/src/forge/_generated/queryRegistry.ts +3 -3
- package/src/forge/_generated/react.d.ts +1 -1
- package/src/forge/_generated/react.ts +1 -1
- package/src/forge/_generated/reactManifest.json +2 -2
- package/src/forge/_generated/reactManifest.ts +3 -3
- package/src/forge/_generated/releaseManifest.json +2 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/_generated/rlsPolicies.json +1 -1
- package/src/forge/_generated/rlsPolicies.sql +1 -1
- package/src/forge/_generated/rlsPolicies.ts +1 -1
- package/src/forge/_generated/runtimeGraph.json +2 -2
- package/src/forge/_generated/runtimeGraph.ts +3 -3
- package/src/forge/_generated/runtimeMatrix.json +2 -2
- package/src/forge/_generated/runtimeMatrix.ts +8684 -1939
- package/src/forge/_generated/runtimeRegistry.ts +1 -1
- package/src/forge/_generated/runtimeRules.md +13 -1
- package/src/forge/_generated/secretRegistry.json +1 -1
- package/src/forge/_generated/secretRegistry.ts +1 -1
- package/src/forge/_generated/secretsContext.ts +1 -1
- package/src/forge/_generated/serverApi.ts +1 -1
- package/src/forge/_generated/sourceMapManifest.json +2 -2
- package/src/forge/_generated/sourceMapManifest.ts +2 -2
- package/src/forge/_generated/sqlPlan.json +1 -1
- package/src/forge/_generated/sqlPlan.ts +1 -1
- package/src/forge/_generated/subscriptionManifest.json +2 -2
- package/src/forge/_generated/subscriptionManifest.ts +3 -3
- package/src/forge/_generated/symbolicationManifest.json +2 -2
- package/src/forge/_generated/symbolicationManifest.ts +2 -2
- package/src/forge/_generated/telemetryRegistry.json +2 -2
- package/src/forge/_generated/telemetryRegistry.ts +3 -3
- package/src/forge/_generated/telemetrySinks.json +2 -2
- package/src/forge/_generated/telemetrySinks.ts +2 -2
- package/src/forge/_generated/tenantScope.json +2 -2
- package/src/forge/_generated/tenantScope.ts +3 -3
- package/src/forge/_generated/testGraph.json +2 -2
- package/src/forge/_generated/testGraph.ts +339 -17
- package/src/forge/_generated/testPlanRegistry.json +2 -2
- package/src/forge/_generated/testPlanRegistry.ts +2 -2
- package/src/forge/_generated/uiRoutes.json +1 -1
- package/src/forge/_generated/uiRoutes.ts +1 -1
- package/src/forge/_generated/uiScenarios.json +1 -1
- package/src/forge/_generated/uiScenarios.ts +1 -1
- package/src/forge/_generated/uiTestManifest.json +2 -2
- package/src/forge/_generated/uiTestManifest.ts +2 -2
- package/src/forge/_generated/workflowRegistry.json +2 -2
- package/src/forge/_generated/workflowRegistry.ts +3 -3
- package/src/forge/_generated/workflowSubscriptions.json +2 -2
- package/src/forge/_generated/workflowSubscriptions.ts +3 -3
- package/src/forge/cli/ai.ts +351 -1
- package/src/forge/cli/auth.ts +36 -1
- package/src/forge/cli/commands.ts +19 -0
- package/src/forge/cli/parse.ts +67 -8
- package/src/forge/cli/rls.ts +529 -17
- package/src/forge/cli/secrets.ts +46 -1
- package/src/forge/cli/security.ts +269 -0
- package/src/forge/compiler/agent-contract/build.ts +289 -8
- package/src/forge/compiler/agent-contract/types.ts +43 -0
- package/src/forge/compiler/ai-registry/build.ts +62 -1
- package/src/forge/compiler/ai-registry/constants.ts +1 -1
- package/src/forge/compiler/ai-registry/parse.ts +98 -4
- package/src/forge/compiler/app-graph/forge-apis.ts +1 -0
- package/src/forge/compiler/dev-manifest/build.ts +3 -0
- package/src/forge/compiler/diagnostics/codes.ts +15 -0
- package/src/forge/compiler/diagnostics/create.ts +1 -1
- package/src/forge/compiler/make-registry/build.ts +13 -0
- package/src/forge/compiler/orchestrator/plan.ts +11 -0
- package/src/forge/compiler/orchestrator/serialize.ts +68 -0
- package/src/forge/compiler/package-graph/compiler.ts +13 -3
- package/src/forge/compiler/types/ai-registry.ts +25 -1
- package/src/forge/compiler/types/app-graph.ts +1 -0
- package/src/forge/compiler/types/cli.ts +1 -0
- package/src/forge/compiler/types/dev-manifest.ts +3 -0
- package/src/forge/dev/server.ts +508 -1
- package/src/forge/make/index.ts +126 -3
- package/src/forge/make/templates.ts +188 -0
- package/src/forge/make/types.ts +1 -0
- package/src/forge/runtime/ai/context.ts +210 -5
- package/src/forge/runtime/ai/types.ts +70 -0
- package/src/forge/runtime/auth/claims.ts +32 -0
- package/src/forge/runtime/auth/errors.ts +2 -0
- package/src/forge/runtime/context/create-context.ts +30 -6
- package/src/forge/runtime/db/memory-adapter.ts +2 -2
- package/src/forge/runtime/telemetry/scrubber.ts +56 -5
- package/src/forge/runtime/webhooks/security.ts +184 -0
- package/src/forge/server.ts +93 -0
- package/src/forge/version.ts +1 -1
- package/templates/b2b-support-web/package.json +1 -0
- package/templates/b2b-support-web/tsconfig.json +4 -1
- package/templates/minimal-web/package.json +1 -0
- package/templates/minimal-web/tsconfig.json +3 -1
package/src/forge/cli/ai.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import { createDiagnostic } from "../compiler/diagnostics/create.ts";
|
|
3
|
+
import type { SqlPlan } from "../compiler/data-graph/sql/types.ts";
|
|
4
|
+
import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
|
|
5
|
+
import { nodeFileSystem } from "../compiler/fs/index.ts";
|
|
6
|
+
import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
|
|
7
|
+
import { createDbAdapter } from "../runtime/db/factory.ts";
|
|
8
|
+
import type { DbAdapterKind } from "../runtime/db/adapter.ts";
|
|
9
|
+
import { applyMigrations } from "../runtime/db/migrate.ts";
|
|
2
10
|
import { createAiContext } from "../runtime/ai/context.ts";
|
|
3
11
|
import {
|
|
4
12
|
checkAiProviders,
|
|
@@ -10,11 +18,36 @@ import { enqueueMockAiResponse } from "../runtime/ai/mock.ts";
|
|
|
10
18
|
import { getRuntimeEnvStore, initializeRuntimeEnv } from "../runtime/context/create-context.ts";
|
|
11
19
|
import { createNoopTelemetryContext } from "../runtime/telemetry/context.ts";
|
|
12
20
|
import { generateTraceId } from "../runtime/telemetry/correlation.ts";
|
|
21
|
+
import { inspectTrace } from "../runtime/telemetry/flush.ts";
|
|
13
22
|
import { loadSecretRegistry } from "../runtime/secrets/check.ts";
|
|
14
23
|
import { createRuntimeSecretsBundle } from "../runtime/secrets/runtime-bundle.ts";
|
|
15
24
|
import type { ForgeAiProvider } from "../runtime/ai/types.ts";
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
function loadAgentTools(workspaceRoot: string): { explicitTools?: unknown[]; autoTools?: unknown[]; agents?: unknown[] } | null {
|
|
27
|
+
const path = join(workspaceRoot, GENERATED_DIR, "agentTools.json");
|
|
28
|
+
if (!nodeFileSystem.exists(path)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(stripDeterministicHeader(nodeFileSystem.readText(path) ?? "{}")) as {
|
|
33
|
+
explicitTools?: unknown[];
|
|
34
|
+
autoTools?: unknown[];
|
|
35
|
+
agents?: unknown[];
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type AiSubcommand =
|
|
43
|
+
| "providers"
|
|
44
|
+
| "check"
|
|
45
|
+
| "test"
|
|
46
|
+
| "models"
|
|
47
|
+
| "tools"
|
|
48
|
+
| "agents"
|
|
49
|
+
| "redteam"
|
|
50
|
+
| "trace";
|
|
18
51
|
|
|
19
52
|
export interface AiCommandOptions {
|
|
20
53
|
subcommand: AiSubcommand;
|
|
@@ -24,6 +57,9 @@ export interface AiCommandOptions {
|
|
|
24
57
|
model?: string;
|
|
25
58
|
prompt?: string;
|
|
26
59
|
mock?: boolean;
|
|
60
|
+
traceId?: string;
|
|
61
|
+
db?: DbAdapterKind;
|
|
62
|
+
databaseUrl?: string;
|
|
27
63
|
}
|
|
28
64
|
|
|
29
65
|
export interface AiCommandResult {
|
|
@@ -32,6 +68,246 @@ export interface AiCommandResult {
|
|
|
32
68
|
diagnostics?: ReturnType<typeof createDiagnostic>[];
|
|
33
69
|
}
|
|
34
70
|
|
|
71
|
+
export interface AiTraceSummary {
|
|
72
|
+
traceId: string;
|
|
73
|
+
agents: Array<Record<string, unknown>>;
|
|
74
|
+
tools: Array<Record<string, unknown>>;
|
|
75
|
+
generations: Array<Record<string, unknown>>;
|
|
76
|
+
usage: Array<Record<string, unknown>>;
|
|
77
|
+
failures: Array<Record<string, unknown>>;
|
|
78
|
+
events: Array<Record<string, unknown>>;
|
|
79
|
+
spans: Array<Record<string, unknown>>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readGeneratedJson<T>(workspaceRoot: string, relative: string): T | null {
|
|
83
|
+
const path = join(workspaceRoot, relative);
|
|
84
|
+
if (!nodeFileSystem.exists(path)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(stripDeterministicHeader(nodeFileSystem.readText(path) ?? "{}")) as T;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function payloadOf(event: Record<string, unknown>): Record<string, unknown> {
|
|
95
|
+
const payload = event.payload;
|
|
96
|
+
if (typeof payload === "string") {
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(payload) as Record<string, unknown>;
|
|
99
|
+
} catch {
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return payload && typeof payload === "object" ? payload as Record<string, unknown> : {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function eventName(payload: Record<string, unknown>): string {
|
|
107
|
+
const event = payload.event;
|
|
108
|
+
if (event && typeof event === "object" && "name" in event) {
|
|
109
|
+
return String((event as { name?: unknown }).name ?? "");
|
|
110
|
+
}
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function eventProperties(payload: Record<string, unknown>): Record<string, unknown> {
|
|
115
|
+
const event = payload.event;
|
|
116
|
+
if (event && typeof event === "object" && "properties" in event) {
|
|
117
|
+
const properties = (event as { properties?: unknown }).properties;
|
|
118
|
+
return properties && typeof properties === "object" ? properties as Record<string, unknown> : {};
|
|
119
|
+
}
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface AgentToolRecord {
|
|
124
|
+
name?: unknown;
|
|
125
|
+
risk?: unknown;
|
|
126
|
+
needsApproval?: unknown;
|
|
127
|
+
readOnly?: unknown;
|
|
128
|
+
sourceKind?: unknown;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface AgentRecord {
|
|
132
|
+
name?: unknown;
|
|
133
|
+
stopWhen?: unknown;
|
|
134
|
+
maxSteps?: unknown;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface AiRedteamScenario {
|
|
138
|
+
id: string;
|
|
139
|
+
name: string;
|
|
140
|
+
status: "passed" | "failed" | "not-applicable";
|
|
141
|
+
evidence: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function toolName(tool: AgentToolRecord): string {
|
|
145
|
+
return typeof tool.name === "string" && tool.name.length > 0 ? tool.name : "<anonymous>";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function toolNeedsApproval(tool: AgentToolRecord): boolean {
|
|
149
|
+
return tool.needsApproval === true || tool.needsApproval === "dynamic";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isDangerousRisk(risk: unknown): boolean {
|
|
153
|
+
return risk === "write" || risk === "external" || risk === "destructive";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function hasAgentStepLimit(agent: AgentRecord): boolean {
|
|
157
|
+
if (typeof agent.maxSteps === "number" && agent.maxSteps > 0) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
const stopWhen = agent.stopWhen;
|
|
161
|
+
return Boolean(
|
|
162
|
+
stopWhen &&
|
|
163
|
+
typeof stopWhen === "object" &&
|
|
164
|
+
"maxSteps" in stopWhen &&
|
|
165
|
+
typeof (stopWhen as { maxSteps?: unknown }).maxSteps === "number" &&
|
|
166
|
+
(stopWhen as { maxSteps: number }).maxSteps > 0,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function runAgentRedteam(workspaceRoot: string): AiCommandResult {
|
|
171
|
+
const agentTools = loadAgentTools(workspaceRoot);
|
|
172
|
+
const explicitTools = (agentTools?.explicitTools ?? []) as AgentToolRecord[];
|
|
173
|
+
const autoTools = (agentTools?.autoTools ?? []) as AgentToolRecord[];
|
|
174
|
+
const agents = (agentTools?.agents ?? []) as AgentRecord[];
|
|
175
|
+
const allTools = [...explicitTools, ...autoTools];
|
|
176
|
+
const dangerousTools = allTools.filter((tool) => isDangerousRisk(tool.risk));
|
|
177
|
+
const dangerousWithoutApproval = dangerousTools.filter((tool) => !toolNeedsApproval(tool));
|
|
178
|
+
const readToolsWithWriteSurface = allTools.filter(
|
|
179
|
+
(tool) => tool.risk === "read" && tool.readOnly === false,
|
|
180
|
+
);
|
|
181
|
+
const commandToolsWithoutApproval = autoTools.filter(
|
|
182
|
+
(tool) => tool.sourceKind === "command" && !toolNeedsApproval(tool),
|
|
183
|
+
);
|
|
184
|
+
const agentsWithoutStepLimit = agents.filter((agent) => !hasAgentStepLimit(agent));
|
|
185
|
+
const secretLikeTools = allTools.filter((tool) => /secret|token|api[_-]?key/i.test(toolName(tool)));
|
|
186
|
+
|
|
187
|
+
const scenarios: AiRedteamScenario[] = [
|
|
188
|
+
{
|
|
189
|
+
id: "approval-bypass",
|
|
190
|
+
name: "Dangerous tools require approval",
|
|
191
|
+
status:
|
|
192
|
+
dangerousTools.length === 0
|
|
193
|
+
? "not-applicable"
|
|
194
|
+
: dangerousWithoutApproval.length === 0
|
|
195
|
+
? "passed"
|
|
196
|
+
: "failed",
|
|
197
|
+
evidence:
|
|
198
|
+
dangerousWithoutApproval.length === 0
|
|
199
|
+
? `${dangerousTools.length} dangerous tools require approval`
|
|
200
|
+
: `tools without approval: ${dangerousWithoutApproval.map(toolName).sort().join(", ")}`,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "auto-command-approval",
|
|
204
|
+
name: "Generated command auto-tools require approval",
|
|
205
|
+
status:
|
|
206
|
+
autoTools.filter((tool) => tool.sourceKind === "command").length === 0
|
|
207
|
+
? "not-applicable"
|
|
208
|
+
: commandToolsWithoutApproval.length === 0
|
|
209
|
+
? "passed"
|
|
210
|
+
: "failed",
|
|
211
|
+
evidence:
|
|
212
|
+
commandToolsWithoutApproval.length === 0
|
|
213
|
+
? "command auto-tools are approval gated"
|
|
214
|
+
: `command auto-tools without approval: ${commandToolsWithoutApproval.map(toolName).sort().join(", ")}`,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "read-only-boundary",
|
|
218
|
+
name: "Read tools stay read-only",
|
|
219
|
+
status: readToolsWithWriteSurface.length === 0 ? "passed" : "failed",
|
|
220
|
+
evidence:
|
|
221
|
+
readToolsWithWriteSurface.length === 0
|
|
222
|
+
? "read tools do not expose write metadata"
|
|
223
|
+
: `read tools with write surface: ${readToolsWithWriteSurface.map(toolName).sort().join(", ")}`,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: "excessive-agency",
|
|
227
|
+
name: "Agents have bounded step limits",
|
|
228
|
+
status:
|
|
229
|
+
agents.length === 0
|
|
230
|
+
? "not-applicable"
|
|
231
|
+
: agentsWithoutStepLimit.length === 0
|
|
232
|
+
? "passed"
|
|
233
|
+
: "failed",
|
|
234
|
+
evidence:
|
|
235
|
+
agentsWithoutStepLimit.length === 0
|
|
236
|
+
? `${agents.length} agents are step bounded`
|
|
237
|
+
: `agents without step limits: ${agentsWithoutStepLimit.map((agent) => String(agent.name ?? "<anonymous>")).sort().join(", ")}`,
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "secret-extraction-surface",
|
|
241
|
+
name: "Tool names do not expose secret-like capabilities",
|
|
242
|
+
status: secretLikeTools.length === 0 ? "passed" : "failed",
|
|
243
|
+
evidence:
|
|
244
|
+
secretLikeTools.length === 0
|
|
245
|
+
? "no secret-like tool names detected"
|
|
246
|
+
: `secret-like tools: ${secretLikeTools.map(toolName).sort().join(", ")}`,
|
|
247
|
+
},
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
const failed = scenarios.filter((scenario) => scenario.status === "failed");
|
|
251
|
+
return {
|
|
252
|
+
exitCode: failed.length === 0 ? 0 : 1,
|
|
253
|
+
data: {
|
|
254
|
+
schemaVersion: "0.1.0",
|
|
255
|
+
kind: "agent-redteam",
|
|
256
|
+
ok: failed.length === 0,
|
|
257
|
+
assurance: "structural-redteam",
|
|
258
|
+
scenarios,
|
|
259
|
+
summary: {
|
|
260
|
+
passed: scenarios.filter((scenario) => scenario.status === "passed").map((scenario) => scenario.id),
|
|
261
|
+
failed: failed.map((scenario) => scenario.id),
|
|
262
|
+
notApplicable: scenarios
|
|
263
|
+
.filter((scenario) => scenario.status === "not-applicable")
|
|
264
|
+
.map((scenario) => scenario.id),
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
diagnostics: failed.map((scenario) =>
|
|
268
|
+
createDiagnostic({
|
|
269
|
+
severity: "error",
|
|
270
|
+
code: "FORGE_AI_REDTEAM_FAILED",
|
|
271
|
+
message: `${scenario.name}: ${scenario.evidence}`,
|
|
272
|
+
fixHint: "Review generated agentTools.json and require approval for write/external/destructive tools, read-only metadata for read tools, and max step limits for agents.",
|
|
273
|
+
suggestedCommands: ["forge ai tools --json", "forge ai agents --json", "forge ai redteam --json"],
|
|
274
|
+
docs: ["docs/ai-agents.md", "docs/threat-model.md"],
|
|
275
|
+
}),
|
|
276
|
+
),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function summarizeAiTrace(traceId: string, inspected: {
|
|
281
|
+
events: Record<string, unknown>[];
|
|
282
|
+
spans: Record<string, unknown>[];
|
|
283
|
+
}): AiTraceSummary {
|
|
284
|
+
const aiEvents = inspected.events
|
|
285
|
+
.map((event) => {
|
|
286
|
+
const payload = payloadOf(event);
|
|
287
|
+
const name = eventName(payload);
|
|
288
|
+
return {
|
|
289
|
+
id: event.id,
|
|
290
|
+
name,
|
|
291
|
+
status: event.status,
|
|
292
|
+
createdAt: event.created_at,
|
|
293
|
+
properties: eventProperties(payload),
|
|
294
|
+
};
|
|
295
|
+
})
|
|
296
|
+
.filter((event) => event.name.startsWith("forge.ai."));
|
|
297
|
+
|
|
298
|
+
const bySuffix = (suffix: string) => aiEvents.filter((event) => event.name.endsWith(suffix));
|
|
299
|
+
return {
|
|
300
|
+
traceId,
|
|
301
|
+
agents: aiEvents.filter((event) => event.name.startsWith("forge.ai.agent.")),
|
|
302
|
+
tools: aiEvents.filter((event) => event.name.startsWith("forge.ai.tool.")),
|
|
303
|
+
generations: aiEvents.filter((event) => event.name.startsWith("forge.ai.generation.") || event.name.startsWith("forge.ai.stream.")),
|
|
304
|
+
usage: aiEvents.filter((event) => event.name === "forge.ai.usage"),
|
|
305
|
+
failures: [...bySuffix(".failed")],
|
|
306
|
+
events: aiEvents,
|
|
307
|
+
spans: inspected.spans,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
35
311
|
export async function runAiCommand(options: AiCommandOptions): Promise<AiCommandResult> {
|
|
36
312
|
initializeRuntimeEnv(options.workspaceRoot);
|
|
37
313
|
const registry = loadAiRegistry(options.workspaceRoot);
|
|
@@ -47,6 +323,58 @@ export async function runAiCommand(options: AiCommandOptions): Promise<AiCommand
|
|
|
47
323
|
const models = loadAiModels(options.workspaceRoot);
|
|
48
324
|
return { exitCode: 0, data: { models } };
|
|
49
325
|
}
|
|
326
|
+
case "tools": {
|
|
327
|
+
const agentTools = loadAgentTools(options.workspaceRoot);
|
|
328
|
+
return {
|
|
329
|
+
exitCode: 0,
|
|
330
|
+
data: {
|
|
331
|
+
explicitTools: agentTools?.explicitTools ?? registry?.tools ?? [],
|
|
332
|
+
autoTools: agentTools?.autoTools ?? [],
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
case "agents": {
|
|
337
|
+
const agentTools = loadAgentTools(options.workspaceRoot);
|
|
338
|
+
return { exitCode: 0, data: { agents: agentTools?.agents ?? registry?.agents ?? [] } };
|
|
339
|
+
}
|
|
340
|
+
case "redteam": {
|
|
341
|
+
return runAgentRedteam(options.workspaceRoot);
|
|
342
|
+
}
|
|
343
|
+
case "trace": {
|
|
344
|
+
if (!options.traceId) {
|
|
345
|
+
return {
|
|
346
|
+
exitCode: 1,
|
|
347
|
+
diagnostics: [
|
|
348
|
+
createDiagnostic({
|
|
349
|
+
severity: "error",
|
|
350
|
+
code: "FORGE_CLI_USAGE",
|
|
351
|
+
message: "forge ai trace requires a trace id",
|
|
352
|
+
}),
|
|
353
|
+
],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const { adapter, diagnostics } = await createDbAdapter({
|
|
357
|
+
kind: options.db ?? "pglite",
|
|
358
|
+
workspaceRoot: options.workspaceRoot,
|
|
359
|
+
databaseUrl: options.databaseUrl,
|
|
360
|
+
});
|
|
361
|
+
if (!adapter) {
|
|
362
|
+
return { exitCode: 1, diagnostics };
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const sqlPlan = readGeneratedJson<SqlPlan>(
|
|
366
|
+
options.workspaceRoot,
|
|
367
|
+
`${GENERATED_DIR}/sqlPlan.json`,
|
|
368
|
+
);
|
|
369
|
+
if (sqlPlan) {
|
|
370
|
+
await applyMigrations(adapter, sqlPlan);
|
|
371
|
+
}
|
|
372
|
+
const inspected = await inspectTrace(adapter, options.traceId);
|
|
373
|
+
return { exitCode: 0, data: summarizeAiTrace(options.traceId, inspected) };
|
|
374
|
+
} finally {
|
|
375
|
+
await adapter.close();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
50
378
|
case "check": {
|
|
51
379
|
const result = checkAiProviders(store, registry, secretRegistry);
|
|
52
380
|
return { exitCode: result.ok ? 0 : 1, data: result };
|
|
@@ -133,6 +461,28 @@ export function formatAiHuman(subcommand: AiSubcommand, result: AiCommandResult)
|
|
|
133
461
|
return `${models.map((m) => JSON.stringify(m)).join("\n")}\n`;
|
|
134
462
|
}
|
|
135
463
|
|
|
464
|
+
if (subcommand === "tools") {
|
|
465
|
+
const data = result.data as { explicitTools?: unknown[]; autoTools?: unknown[] };
|
|
466
|
+
const tools = [
|
|
467
|
+
...(data.explicitTools ?? []),
|
|
468
|
+
...(data.autoTools ?? []),
|
|
469
|
+
];
|
|
470
|
+
return `${tools.map((m) => JSON.stringify(m)).join("\n")}\n`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (subcommand === "agents") {
|
|
474
|
+
const agents = (result.data as { agents: unknown[] })?.agents ?? [];
|
|
475
|
+
return `${agents.map((m) => JSON.stringify(m)).join("\n")}\n`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (subcommand === "trace") {
|
|
479
|
+
return `${JSON.stringify(result.data, null, 2)}\n`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (subcommand === "redteam") {
|
|
483
|
+
return `${JSON.stringify(result.data, null, 2)}\n`;
|
|
484
|
+
}
|
|
485
|
+
|
|
136
486
|
if (subcommand === "check") {
|
|
137
487
|
const data = result.data as { ok: boolean; missing: string[] };
|
|
138
488
|
return data.ok
|
package/src/forge/cli/auth.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { mapClaimsToAuthContext } from "../runtime/auth/claims.ts";
|
|
|
9
9
|
import { ForgeAuthError } from "../runtime/auth/errors.ts";
|
|
10
10
|
import { verifyJwtToken } from "../runtime/auth/verifier.ts";
|
|
11
11
|
|
|
12
|
-
export type AuthSubcommand = "check" | "config" | "decode" | "test-token" | "jwks";
|
|
12
|
+
export type AuthSubcommand = "check" | "config" | "decode" | "test-token" | "jwks" | "prove";
|
|
13
13
|
|
|
14
14
|
export interface AuthCommandOptions {
|
|
15
15
|
subcommand: AuthSubcommand;
|
|
@@ -163,6 +163,41 @@ export async function runAuthCommand(
|
|
|
163
163
|
if (options.subcommand === "check") {
|
|
164
164
|
return validateConfig(options.workspaceRoot);
|
|
165
165
|
}
|
|
166
|
+
if (options.subcommand === "prove") {
|
|
167
|
+
const checked = validateConfig(options.workspaceRoot);
|
|
168
|
+
const config = loadAuthConfigFromEnv(options.workspaceRoot);
|
|
169
|
+
const productionMode = config.mode === "jwt" || config.mode === "oidc";
|
|
170
|
+
return {
|
|
171
|
+
ok: checked.ok,
|
|
172
|
+
mode: config.mode,
|
|
173
|
+
data: {
|
|
174
|
+
schemaVersion: "0.1.0",
|
|
175
|
+
kind: "auth-proof",
|
|
176
|
+
ok: checked.ok,
|
|
177
|
+
mode: config.mode,
|
|
178
|
+
productionReady: productionMode && checked.ok,
|
|
179
|
+
invariants: [
|
|
180
|
+
{
|
|
181
|
+
id: "INV-001",
|
|
182
|
+
name: "dev headers are not production auth",
|
|
183
|
+
status: productionMode ? "passed" : "local-only",
|
|
184
|
+
evidence: productionMode
|
|
185
|
+
? "jwt/oidc mode configured through environment"
|
|
186
|
+
: "dev-headers mode is allowed only for local dev, tests, and agent workflows",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: "INV-001-CONFIG",
|
|
190
|
+
name: "jwt/oidc required settings are present when production auth is enabled",
|
|
191
|
+
status: checked.ok ? "passed" : "failed",
|
|
192
|
+
evidence: checked.data,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
checkedAt: "deterministic",
|
|
196
|
+
},
|
|
197
|
+
error: checked.error,
|
|
198
|
+
exitCode: checked.exitCode,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
166
201
|
if (options.subcommand === "config") {
|
|
167
202
|
return publicConfig(options.workspaceRoot);
|
|
168
203
|
}
|
|
@@ -102,6 +102,11 @@ import {
|
|
|
102
102
|
} from "./windows.ts";
|
|
103
103
|
import { formatAuthHuman, formatAuthJson, runAuthCommand } from "./auth.ts";
|
|
104
104
|
import { formatRlsHuman, formatRlsJson, runRlsCommand } from "./rls.ts";
|
|
105
|
+
import {
|
|
106
|
+
formatSecurityHuman,
|
|
107
|
+
formatSecurityJson,
|
|
108
|
+
runSecurityCommand,
|
|
109
|
+
} from "./security.ts";
|
|
105
110
|
import { formatDepsHuman, formatDepsJson, runDepsCommand } from "./deps.ts";
|
|
106
111
|
import {
|
|
107
112
|
formatReleaseHuman,
|
|
@@ -405,6 +410,7 @@ export async function runInspectCommand(
|
|
|
405
410
|
"test-graph": `${GENERATED_DIR}/testGraph.json`,
|
|
406
411
|
"test-plans": `${GENERATED_DIR}/testPlanRegistry.json`,
|
|
407
412
|
"agent-contract": `${GENERATED_DIR}/agentContract.json`,
|
|
413
|
+
"agent-tools": `${GENERATED_DIR}/agentTools.json`,
|
|
408
414
|
"agent-adapters": `${GENERATED_DIR}/agentAdapterManifest.json`,
|
|
409
415
|
"capability-map": `${GENERATED_DIR}/capabilityMap.json`,
|
|
410
416
|
ui: `${GENERATED_DIR}/uiTestManifest.json`,
|
|
@@ -451,6 +457,7 @@ export async function runInspectCommand(
|
|
|
451
457
|
["testGraph", `${GENERATED_DIR}/testGraph.json`],
|
|
452
458
|
["testPlanRegistry", `${GENERATED_DIR}/testPlanRegistry.json`],
|
|
453
459
|
["agentContract", `${GENERATED_DIR}/agentContract.json`],
|
|
460
|
+
["agentTools", `${GENERATED_DIR}/agentTools.json`],
|
|
454
461
|
["agentAdapters", `${GENERATED_DIR}/agentAdapterManifest.json`],
|
|
455
462
|
["capabilityMap", `${GENERATED_DIR}/capabilityMap.json`],
|
|
456
463
|
["ui", `${GENERATED_DIR}/uiTestManifest.json`],
|
|
@@ -634,6 +641,15 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
634
641
|
}
|
|
635
642
|
return result.exitCode;
|
|
636
643
|
}
|
|
644
|
+
case "security": {
|
|
645
|
+
const result = await runSecurityCommand(command);
|
|
646
|
+
if (command.json) {
|
|
647
|
+
process.stdout.write(formatSecurityJson(result));
|
|
648
|
+
} else {
|
|
649
|
+
process.stdout.write(formatSecurityHuman(result));
|
|
650
|
+
}
|
|
651
|
+
return result.exitCode;
|
|
652
|
+
}
|
|
637
653
|
case "auth": {
|
|
638
654
|
const result = await runAuthCommand(command);
|
|
639
655
|
if (command.json) {
|
|
@@ -1111,6 +1127,9 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1111
1127
|
model: command.model,
|
|
1112
1128
|
prompt: command.prompt,
|
|
1113
1129
|
mock: command.mock,
|
|
1130
|
+
traceId: command.traceId,
|
|
1131
|
+
db: command.db,
|
|
1132
|
+
databaseUrl: command.databaseUrl,
|
|
1114
1133
|
});
|
|
1115
1134
|
|
|
1116
1135
|
if (command.json) {
|
package/src/forge/cli/parse.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { SelfHostSubcommand } from "./self-host.ts";
|
|
|
17
17
|
import type { AgentContractSubcommand } from "./agent-contract.ts";
|
|
18
18
|
import type { AuthSubcommand } from "./auth.ts";
|
|
19
19
|
import type { RlsSubcommand } from "./rls.ts";
|
|
20
|
+
import type { SecuritySubcommand } from "./security.ts";
|
|
20
21
|
import type { DepsSubcommand } from "./deps.ts";
|
|
21
22
|
import type { ReleaseAction, ReleaseArea } from "./release.ts";
|
|
22
23
|
import type { MakeCommandOptions, MakePrimitive } from "../make/types.ts";
|
|
@@ -99,6 +100,14 @@ export type ForgeCommand =
|
|
|
99
100
|
}
|
|
100
101
|
| { kind: "doctor"; target?: "project" | "windows"; json: boolean; workspaceRoot: string }
|
|
101
102
|
| { kind: "setup"; target: "windows"; json: boolean; yes: boolean; workspaceRoot: string }
|
|
103
|
+
| {
|
|
104
|
+
kind: "security";
|
|
105
|
+
subcommand: SecuritySubcommand;
|
|
106
|
+
db: DbAdapterKind;
|
|
107
|
+
databaseUrl?: string;
|
|
108
|
+
json: boolean;
|
|
109
|
+
workspaceRoot: string;
|
|
110
|
+
}
|
|
102
111
|
| {
|
|
103
112
|
kind: "auth";
|
|
104
113
|
subcommand: AuthSubcommand;
|
|
@@ -284,6 +293,9 @@ export type ForgeCommand =
|
|
|
284
293
|
model?: string;
|
|
285
294
|
prompt?: string;
|
|
286
295
|
mock: boolean;
|
|
296
|
+
traceId?: string;
|
|
297
|
+
db?: DbAdapterKind;
|
|
298
|
+
databaseUrl?: string;
|
|
287
299
|
workspaceRoot: string;
|
|
288
300
|
};
|
|
289
301
|
|
|
@@ -305,6 +317,7 @@ export const TOP_LEVEL_COMMANDS = [
|
|
|
305
317
|
"ui",
|
|
306
318
|
"doctor",
|
|
307
319
|
"setup",
|
|
320
|
+
"security",
|
|
308
321
|
"auth",
|
|
309
322
|
"rls",
|
|
310
323
|
"deps",
|
|
@@ -367,6 +380,7 @@ export const INSPECT_TARGETS: InspectTarget[] = [
|
|
|
367
380
|
"test-graph",
|
|
368
381
|
"test-plans",
|
|
369
382
|
"agent-contract",
|
|
383
|
+
"agent-tools",
|
|
370
384
|
"agent-adapters",
|
|
371
385
|
"capability-map",
|
|
372
386
|
"framework",
|
|
@@ -392,8 +406,10 @@ const AUTH_SUBCOMMANDS: AuthSubcommand[] = [
|
|
|
392
406
|
"decode",
|
|
393
407
|
"test-token",
|
|
394
408
|
"jwks",
|
|
409
|
+
"prove",
|
|
395
410
|
];
|
|
396
|
-
const
|
|
411
|
+
const SECURITY_SUBCOMMANDS: SecuritySubcommand[] = ["prove"];
|
|
412
|
+
const RLS_SUBCOMMANDS: RlsSubcommand[] = ["generate", "check", "apply", "test", "mutate-test"];
|
|
397
413
|
const DEPS_SUBCOMMANDS: DepsSubcommand[] = [
|
|
398
414
|
"outdated",
|
|
399
415
|
"inspect",
|
|
@@ -430,6 +446,7 @@ const MAKE_PRIMITIVES: MakePrimitive[] = [
|
|
|
430
446
|
"component",
|
|
431
447
|
"page",
|
|
432
448
|
"ui",
|
|
449
|
+
"ai-chat",
|
|
433
450
|
"resource",
|
|
434
451
|
"apply",
|
|
435
452
|
"rollback",
|
|
@@ -518,6 +535,16 @@ const UI_BROWSERS: UiBrowserName[] = ["chromium", "firefox", "webkit"];
|
|
|
518
535
|
const UI_TRACE_MODES: UiTraceMode[] = ["on", "off", "retain-on-failure"];
|
|
519
536
|
const UI_SCREENSHOT_MODES: UiScreenshotMode[] = ["on", "off", "only-on-failure"];
|
|
520
537
|
const UI_VIDEO_MODES: UiVideoMode[] = ["on", "off", "retain-on-failure"];
|
|
538
|
+
const AI_SUBCOMMANDS: AiSubcommand[] = [
|
|
539
|
+
"providers",
|
|
540
|
+
"check",
|
|
541
|
+
"test",
|
|
542
|
+
"models",
|
|
543
|
+
"tools",
|
|
544
|
+
"agents",
|
|
545
|
+
"redteam",
|
|
546
|
+
"trace",
|
|
547
|
+
];
|
|
521
548
|
|
|
522
549
|
function parseFlag(args: string[], flag: string): boolean {
|
|
523
550
|
return args.includes(flag);
|
|
@@ -549,6 +576,10 @@ function parseDbKind(value: string | undefined): "pglite" | "postgres" | "none"
|
|
|
549
576
|
return "pglite";
|
|
550
577
|
}
|
|
551
578
|
|
|
579
|
+
function parsePersistentDbKind(value: string | undefined): DbAdapterKind {
|
|
580
|
+
return parseDbKind(value) === "postgres" ? "postgres" : "pglite";
|
|
581
|
+
}
|
|
582
|
+
|
|
552
583
|
function parseAdapterKind(value: string | undefined): DbAdapterKind {
|
|
553
584
|
if (value === "postgres" || value === "memory") {
|
|
554
585
|
return value;
|
|
@@ -969,10 +1000,29 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
969
1000
|
errors,
|
|
970
1001
|
};
|
|
971
1002
|
}
|
|
1003
|
+
case "security": {
|
|
1004
|
+
const subcommand = rest[0] as SecuritySubcommand | undefined;
|
|
1005
|
+
if (!subcommand || !SECURITY_SUBCOMMANDS.includes(subcommand)) {
|
|
1006
|
+
errors.push("forge security requires subcommand: prove");
|
|
1007
|
+
return { command: null, workspaceRoot, errors };
|
|
1008
|
+
}
|
|
1009
|
+
return {
|
|
1010
|
+
command: {
|
|
1011
|
+
kind: "security",
|
|
1012
|
+
subcommand,
|
|
1013
|
+
db: parseAdapterKind(parseOptionValue(argv, "--db")),
|
|
1014
|
+
databaseUrl: parseOptionValue(argv, "--database-url"),
|
|
1015
|
+
json: parseFlag(argv, "--json"),
|
|
1016
|
+
workspaceRoot,
|
|
1017
|
+
},
|
|
1018
|
+
workspaceRoot,
|
|
1019
|
+
errors,
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
972
1022
|
case "auth": {
|
|
973
1023
|
const subcommand = rest[0] as AuthSubcommand | undefined;
|
|
974
1024
|
if (!subcommand || !AUTH_SUBCOMMANDS.includes(subcommand)) {
|
|
975
|
-
errors.push("forge auth requires subcommand: check, config, decode, test-token, or
|
|
1025
|
+
errors.push("forge auth requires subcommand: check, config, decode, test-token, jwks, or prove");
|
|
976
1026
|
return { command: null, workspaceRoot, errors };
|
|
977
1027
|
}
|
|
978
1028
|
return {
|
|
@@ -990,7 +1040,7 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
990
1040
|
case "rls": {
|
|
991
1041
|
const subcommand = rest[0] as RlsSubcommand | undefined;
|
|
992
1042
|
if (!subcommand || !RLS_SUBCOMMANDS.includes(subcommand)) {
|
|
993
|
-
errors.push("forge rls requires subcommand: generate, check, apply, or test");
|
|
1043
|
+
errors.push("forge rls requires subcommand: generate, check, apply, test, or mutate-test");
|
|
994
1044
|
return { command: null, workspaceRoot, errors };
|
|
995
1045
|
}
|
|
996
1046
|
return {
|
|
@@ -1082,6 +1132,8 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1082
1132
|
? undefined
|
|
1083
1133
|
: primitive === "ui"
|
|
1084
1134
|
? rest[1] ?? "ui"
|
|
1135
|
+
: primitive === "ai-chat"
|
|
1136
|
+
? rest[1] ?? "support"
|
|
1085
1137
|
: rest[1];
|
|
1086
1138
|
const explainPrimitive =
|
|
1087
1139
|
primitive === "explain" ? (rest[1] as MakePrimitive | undefined) : undefined;
|
|
@@ -1092,7 +1144,7 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1092
1144
|
errors.push("forge make explain requires a known primitive");
|
|
1093
1145
|
}
|
|
1094
1146
|
if (
|
|
1095
|
-
!["list", "explain", "ui"].includes(primitive) &&
|
|
1147
|
+
!["list", "explain", "ui", "ai-chat"].includes(primitive) &&
|
|
1096
1148
|
!name
|
|
1097
1149
|
) {
|
|
1098
1150
|
errors.push(`forge make ${primitive} requires a name or plan id`);
|
|
@@ -1882,10 +1934,10 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1882
1934
|
const subcommand = rest[0] as SecretsSubcommand | undefined;
|
|
1883
1935
|
if (
|
|
1884
1936
|
!subcommand ||
|
|
1885
|
-
!["list", "check", "print", "set", "unset"].includes(subcommand)
|
|
1937
|
+
!["list", "check", "print", "set", "unset", "prove"].includes(subcommand)
|
|
1886
1938
|
) {
|
|
1887
1939
|
errors.push(
|
|
1888
|
-
"forge secrets requires subcommand: list, check, print, set, or
|
|
1940
|
+
"forge secrets requires subcommand: list, check, print, set, unset, or prove",
|
|
1889
1941
|
);
|
|
1890
1942
|
return { command: null, workspaceRoot, errors };
|
|
1891
1943
|
}
|
|
@@ -1925,13 +1977,17 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1925
1977
|
}
|
|
1926
1978
|
case "ai": {
|
|
1927
1979
|
const subcommand = rest[0] as AiSubcommand | undefined;
|
|
1928
|
-
if (!subcommand || !
|
|
1929
|
-
errors.push("forge ai requires subcommand: providers, check, test, or
|
|
1980
|
+
if (!subcommand || !AI_SUBCOMMANDS.includes(subcommand)) {
|
|
1981
|
+
errors.push("forge ai requires subcommand: providers, check, test, models, tools, agents, redteam, or trace");
|
|
1930
1982
|
return { command: null, workspaceRoot, errors };
|
|
1931
1983
|
}
|
|
1932
1984
|
|
|
1933
1985
|
const providerRaw = parseOptionValue(argv, "--provider");
|
|
1934
1986
|
const provider = providerRaw as ForgeAiProvider | undefined;
|
|
1987
|
+
const traceId = subcommand === "trace" ? rest[1] ?? parseOptionValue(argv, "--trace") : undefined;
|
|
1988
|
+
if (subcommand === "trace" && !traceId) {
|
|
1989
|
+
errors.push("forge ai trace requires a trace id");
|
|
1990
|
+
}
|
|
1935
1991
|
|
|
1936
1992
|
return {
|
|
1937
1993
|
command: {
|
|
@@ -1942,6 +1998,9 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1942
1998
|
model: parseOptionValue(argv, "--model"),
|
|
1943
1999
|
prompt: parseOptionValue(argv, "--prompt"),
|
|
1944
2000
|
mock: parseFlag(argv, "--mock"),
|
|
2001
|
+
traceId,
|
|
2002
|
+
db: parsePersistentDbKind(parseOptionValue(argv, "--db")),
|
|
2003
|
+
databaseUrl: parseOptionValue(argv, "--database-url"),
|
|
1945
2004
|
workspaceRoot,
|
|
1946
2005
|
},
|
|
1947
2006
|
workspaceRoot,
|