@vellumai/assistant 0.10.9-staging.1 → 0.10.9
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 +4 -0
- package/knip.json +1 -4
- package/openapi.yaml +27 -17
- package/package.json +1 -1
- package/src/__tests__/always-loaded-tools-guard.test.ts +4 -6
- package/src/__tests__/app-routes-csp.test.ts +13 -4
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +4 -8
- package/src/__tests__/disk-pressure-tools.test.ts +4 -4
- package/src/__tests__/file-list-tool.test.ts +22 -12
- package/src/__tests__/list-all-apps.test.ts +204 -0
- package/src/__tests__/messaging-send-tool.test.ts +67 -0
- package/src/__tests__/mtime-cache.test.ts +42 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +3 -2
- package/src/__tests__/outlook-messaging-provider.test.ts +71 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +163 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +47 -0
- package/src/__tests__/subagent-notify-parent.test.ts +4 -3
- package/src/__tests__/subagent-tool-filtering.test.ts +21 -21
- package/src/__tests__/subagent-tool-gate-mode.test.ts +6 -6
- package/src/apps/app-store.ts +302 -2
- package/src/cli/AGENTS.md +17 -0
- package/src/cli/commands/__tests__/gateway.test.ts +55 -0
- package/src/cli/commands/apps.help.ts +17 -11
- package/src/cli/commands/apps.ts +5 -15
- package/src/cli/commands/gateway.help.ts +30 -0
- package/src/cli/commands/gateway.ts +40 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -90
- package/src/cli/commands/platform/index.help.ts +3 -2
- package/src/cli/commands/platform/index.ts +0 -9
- package/src/cli/commands/routes.help.ts +13 -7
- package/src/cli/commands/routes.ts +5 -3
- package/src/cli/commands/status.help.ts +24 -0
- package/src/cli/commands/status.ts +29 -0
- package/src/cli/lib/__tests__/global-json-option.test.ts +97 -0
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +1 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +31 -0
- package/src/cli/lib/bundled-marketplace.json +187 -173
- package/src/cli/lib/global-json-option.ts +41 -0
- package/src/cli/lib/plugin-fingerprint.ts +18 -7
- package/src/cli/program.ts +5 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +27 -19
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/feature-flag-registry.json +1 -9
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +3 -6
- package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +3 -6
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +8 -5
- package/src/daemon/conversation-tool-setup.ts +5 -90
- package/src/daemon/conversation.ts +2 -2
- package/src/messaging/provider-types.ts +9 -0
- package/src/messaging/providers/outlook/adapter.ts +17 -2
- package/src/messaging/providers/outlook/client.ts +11 -2
- package/src/messaging/providers/outlook/types.ts +9 -0
- package/src/monitoring/__tests__/plugin-source-watch.test.ts +37 -0
- package/src/monitoring/plugin-source-watch.ts +53 -0
- package/src/oauth/seed-providers.ts +6 -0
- package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +3 -0
- package/src/persistence/embeddings/messages-lexical-index.ts +11 -0
- package/src/plugin-api/conversation-turn.ts +234 -0
- package/src/plugin-api/index.ts +12 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +41 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +48 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +46 -5
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +2 -2
- package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +81 -2
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +80 -33
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +40 -1
- package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +28 -2
- package/src/plugins/mtime-cache.ts +26 -13
- package/src/plugins/plugin-tree-walk.ts +30 -0
- package/src/plugins/source-fingerprint.ts +9 -2
- package/src/plugins/surface-import.ts +28 -0
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +1 -1
- package/src/providers/speech-to-text/__tests__/vellum-speech-relay-connection.test.ts +4 -1
- package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +5 -0
- package/src/runtime/routes/__tests__/gateway-status-routes.test.ts +84 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +47 -3
- package/src/runtime/routes/__tests__/user-routes-cli.test.ts +154 -0
- package/src/runtime/routes/app-management-routes.ts +96 -32
- package/src/runtime/routes/app-routes.ts +33 -17
- package/src/runtime/routes/gateway-status-routes.ts +69 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/platform-routes.ts +4 -14
- package/src/runtime/routes/plugins-routes.ts +58 -1
- package/src/runtime/routes/settings-routes.ts +8 -6
- package/src/runtime/routes/user-route-dispatcher.ts +6 -86
- package/src/runtime/routes/user-route-resolution.ts +141 -0
- package/src/runtime/routes/user-routes-cli.ts +56 -35
- package/src/telemetry/AGENTS.md +15 -0
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +258 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire-validation.test.ts +157 -0
- package/src/telemetry/telemetry-wire-validation.ts +125 -0
- package/src/telemetry/telemetry-wire.generated.ts +60 -9
- package/src/telemetry/types.test.ts +38 -0
- package/src/telemetry/types.ts +183 -91
- package/src/telemetry/usage-telemetry-reporter.ts +5 -0
- package/src/telemetry/watchdog-direct-emit.ts +6 -0
- package/src/tools/skills/scaffold-managed.ts +43 -11
|
@@ -18,7 +18,6 @@ let mockResponse: unknown = {
|
|
|
18
18
|
available: false,
|
|
19
19
|
organizationId: null,
|
|
20
20
|
userId: null,
|
|
21
|
-
velayTunnel: null,
|
|
22
21
|
},
|
|
23
22
|
};
|
|
24
23
|
|
|
@@ -63,7 +62,6 @@ describe("assistant platform status", () => {
|
|
|
63
62
|
available: false,
|
|
64
63
|
organizationId: null,
|
|
65
64
|
userId: null,
|
|
66
|
-
velayTunnel: null,
|
|
67
65
|
},
|
|
68
66
|
};
|
|
69
67
|
process.exitCode = 0;
|
|
@@ -81,7 +79,6 @@ describe("assistant platform status", () => {
|
|
|
81
79
|
available: true,
|
|
82
80
|
organizationId: "org-456",
|
|
83
81
|
userId: "user-789",
|
|
84
|
-
velayTunnel: null,
|
|
85
82
|
},
|
|
86
83
|
};
|
|
87
84
|
|
|
@@ -116,93 +113,6 @@ describe("assistant platform status", () => {
|
|
|
116
113
|
expect(parsed.available).toBe(true);
|
|
117
114
|
expect(parsed.organizationId).toBe("org-456");
|
|
118
115
|
expect(parsed.userId).toBe("user-789");
|
|
119
|
-
expect(parsed.velayTunnel).toBeNull();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("velayTunnel connected with publicUrl is returned when gateway is live", async () => {
|
|
123
|
-
mockResponse = {
|
|
124
|
-
ok: true,
|
|
125
|
-
result: {
|
|
126
|
-
isPlatform: false,
|
|
127
|
-
baseUrl: "",
|
|
128
|
-
assistantId: "",
|
|
129
|
-
hasAssistantApiKey: false,
|
|
130
|
-
hasWebhookSecret: false,
|
|
131
|
-
available: false,
|
|
132
|
-
organizationId: null,
|
|
133
|
-
userId: null,
|
|
134
|
-
velayTunnel: {
|
|
135
|
-
connected: true,
|
|
136
|
-
publicUrl: "https://abc123.vellum.ai",
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const program = buildProgram();
|
|
142
|
-
const stdoutChunks: string[] = [];
|
|
143
|
-
const origWrite = process.stdout.write.bind(process.stdout);
|
|
144
|
-
process.stdout.write = ((chunk: unknown) => {
|
|
145
|
-
stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
146
|
-
return true;
|
|
147
|
-
}) as typeof process.stdout.write;
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
await program.parseAsync([
|
|
151
|
-
"node",
|
|
152
|
-
"assistant",
|
|
153
|
-
"platform",
|
|
154
|
-
"status",
|
|
155
|
-
"--json",
|
|
156
|
-
]);
|
|
157
|
-
} finally {
|
|
158
|
-
process.stdout.write = origWrite;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const parsed = JSON.parse(stdoutChunks.join(""));
|
|
162
|
-
expect(parsed.velayTunnel).toEqual({
|
|
163
|
-
connected: true,
|
|
164
|
-
publicUrl: "https://abc123.vellum.ai",
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("velayTunnel disconnected when gateway reports no active connection", async () => {
|
|
169
|
-
mockResponse = {
|
|
170
|
-
ok: true,
|
|
171
|
-
result: {
|
|
172
|
-
isPlatform: false,
|
|
173
|
-
baseUrl: "",
|
|
174
|
-
assistantId: "",
|
|
175
|
-
hasAssistantApiKey: false,
|
|
176
|
-
hasWebhookSecret: false,
|
|
177
|
-
available: false,
|
|
178
|
-
organizationId: null,
|
|
179
|
-
userId: null,
|
|
180
|
-
velayTunnel: { connected: false, publicUrl: null },
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const program = buildProgram();
|
|
185
|
-
const stdoutChunks: string[] = [];
|
|
186
|
-
const origWrite = process.stdout.write.bind(process.stdout);
|
|
187
|
-
process.stdout.write = ((chunk: unknown) => {
|
|
188
|
-
stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
189
|
-
return true;
|
|
190
|
-
}) as typeof process.stdout.write;
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
await program.parseAsync([
|
|
194
|
-
"node",
|
|
195
|
-
"assistant",
|
|
196
|
-
"platform",
|
|
197
|
-
"status",
|
|
198
|
-
"--json",
|
|
199
|
-
]);
|
|
200
|
-
} finally {
|
|
201
|
-
process.stdout.write = origWrite;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const parsed = JSON.parse(stdoutChunks.join(""));
|
|
205
|
-
expect(parsed.velayTunnel).toEqual({ connected: false, publicUrl: null });
|
|
206
116
|
});
|
|
207
117
|
|
|
208
118
|
test("plain text mode does not emit JSON to stdout", async () => {
|
|
@@ -58,8 +58,9 @@ Fields:
|
|
|
58
58
|
available Whether callback registration prerequisites are satisfied
|
|
59
59
|
organizationId The platform organization ID (from stored credentials)
|
|
60
60
|
userId The platform user ID (from stored credentials)
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
|
|
62
|
+
For the Velay tunnel state (only relevant for Twilio webhooks and live
|
|
63
|
+
voice/audio), use 'assistant gateway status' instead.
|
|
63
64
|
|
|
64
65
|
Examples:
|
|
65
66
|
$ assistant platform status
|
|
@@ -24,7 +24,6 @@ interface PlatformStatusResult {
|
|
|
24
24
|
available: boolean;
|
|
25
25
|
organizationId: string | null;
|
|
26
26
|
userId: string | null;
|
|
27
|
-
velayTunnel: { connected: boolean; publicUrl: string | null } | null;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
interface PlatformCreditsResult {
|
|
@@ -87,14 +86,6 @@ export function registerPlatformCommand(program: Command): void {
|
|
|
87
86
|
`Organization ID: ${result.organizationId || "(not set)"}`,
|
|
88
87
|
);
|
|
89
88
|
log.info(`User ID: ${result.userId || "(not set)"}`);
|
|
90
|
-
if (result.velayTunnel !== null) {
|
|
91
|
-
const tunnelState = result.velayTunnel.connected
|
|
92
|
-
? `connected${result.velayTunnel.publicUrl ? ` (${result.velayTunnel.publicUrl})` : ""}`
|
|
93
|
-
: "disconnected";
|
|
94
|
-
log.info(`Velay tunnel: ${tunnelState}`);
|
|
95
|
-
} else {
|
|
96
|
-
log.info(`Velay tunnel: (gateway not running)`);
|
|
97
|
-
}
|
|
98
89
|
}
|
|
99
90
|
},
|
|
100
91
|
);
|
|
@@ -8,8 +8,10 @@ export const routesHelp: CliCommandHelp = {
|
|
|
8
8
|
"Manage user-defined authenticated HTTP route handlers under /x/*",
|
|
9
9
|
helpText: `
|
|
10
10
|
User-defined routes let you expose custom HTTP endpoints by dropping handler
|
|
11
|
-
files into /workspace/routes
|
|
12
|
-
(
|
|
11
|
+
files into /workspace/routes/ (reachable at /x/<path>) or into a plugin's
|
|
12
|
+
/workspace/plugins/<name>/routes/ (reachable in that plugin's namespace at
|
|
13
|
+
/x/plugins/<name>/<path>). Each file exports named HTTP method functions
|
|
14
|
+
(GET, POST, etc.).
|
|
13
15
|
|
|
14
16
|
These routes require edge authentication — they are intended for
|
|
15
17
|
assistant-internal or user-facing endpoints, not for unauthenticated provider
|
|
@@ -21,7 +23,8 @@ needed.
|
|
|
21
23
|
Examples:
|
|
22
24
|
$ assistant routes list
|
|
23
25
|
$ assistant routes list --json
|
|
24
|
-
$ assistant routes inspect my-dashboard-api/submit
|
|
26
|
+
$ assistant routes inspect my-dashboard-api/submit
|
|
27
|
+
$ assistant routes inspect plugins/my-plugin/status`,
|
|
25
28
|
subcommands: [
|
|
26
29
|
{
|
|
27
30
|
name: "list",
|
|
@@ -33,8 +36,9 @@ Examples:
|
|
|
33
36
|
},
|
|
34
37
|
],
|
|
35
38
|
helpText: `
|
|
36
|
-
Scans /workspace/routes/
|
|
37
|
-
|
|
39
|
+
Scans /workspace/routes/ and each enabled plugin's /workspace/plugins/<name>/routes/
|
|
40
|
+
for handler files (.ts, .js) and displays the route path, exported HTTP
|
|
41
|
+
methods, optional description, and file location.
|
|
38
42
|
|
|
39
43
|
Examples:
|
|
40
44
|
$ assistant routes list
|
|
@@ -52,14 +56,16 @@ Examples:
|
|
|
52
56
|
],
|
|
53
57
|
helpText: `
|
|
54
58
|
Arguments:
|
|
55
|
-
path Route path relative to /x/ (e.g. "my-dashboard-api/submit"
|
|
56
|
-
|
|
59
|
+
path Route path relative to /x/ (e.g. "my-dashboard-api/submit" or
|
|
60
|
+
"plugins/my-plugin/status"). The /x/ prefix is optional, so a path
|
|
61
|
+
copied straight from 'routes list' works too.
|
|
57
62
|
|
|
58
63
|
Loads the handler file and displays exported methods, description, file path,
|
|
59
64
|
public URL, file size, and last modified time.
|
|
60
65
|
|
|
61
66
|
Examples:
|
|
62
67
|
$ assistant routes inspect my-dashboard-api/submit
|
|
68
|
+
$ assistant routes inspect plugins/my-plugin/status
|
|
63
69
|
$ assistant routes inspect items --json`,
|
|
64
70
|
},
|
|
65
71
|
],
|
|
@@ -68,7 +68,9 @@ export function registerRoutesCommand(program: Command): void {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
if (discovered.length === 0) {
|
|
71
|
-
log.info(
|
|
71
|
+
log.info(
|
|
72
|
+
"No route handlers found in /workspace/routes/ or /workspace/plugins/<name>/routes/.",
|
|
73
|
+
);
|
|
72
74
|
log.info(
|
|
73
75
|
"Create a .ts or .js file exporting named HTTP method functions (GET, POST, etc.).",
|
|
74
76
|
);
|
|
@@ -108,7 +110,7 @@ export function registerRoutesCommand(program: Command): void {
|
|
|
108
110
|
route.routePath.padEnd(routeWidth),
|
|
109
111
|
formatMethods(route.methods).padEnd(methodsWidth),
|
|
110
112
|
(route.description ?? "").padEnd(descWidth),
|
|
111
|
-
|
|
113
|
+
route.filePath,
|
|
112
114
|
].join(" ");
|
|
113
115
|
log.info(` ${cols}`);
|
|
114
116
|
}
|
|
@@ -130,7 +132,7 @@ export function registerRoutesCommand(program: Command): void {
|
|
|
130
132
|
async (routePath: string, opts: { json?: boolean }) => {
|
|
131
133
|
const r = await cliIpcCall<{ route: InspectedRoute }>(
|
|
132
134
|
"user_routes_inspect",
|
|
133
|
-
{ path: routePath },
|
|
135
|
+
{ body: { path: routePath } },
|
|
134
136
|
);
|
|
135
137
|
if (!r.ok) {
|
|
136
138
|
if (opts.json) {
|
|
@@ -5,4 +5,28 @@ import type { CliCommandHelp } from "../lib/cli-command-help.js";
|
|
|
5
5
|
export const statusHelp: CliCommandHelp = {
|
|
6
6
|
name: "status",
|
|
7
7
|
description: "Show assistant version, workspace, and runtime health",
|
|
8
|
+
options: [
|
|
9
|
+
{
|
|
10
|
+
flags: "--json",
|
|
11
|
+
description: "Emit the status as machine-readable JSON",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
helpText: `
|
|
15
|
+
Behavior:
|
|
16
|
+
Reports the running assistant's version, workspace path, and memory/disk
|
|
17
|
+
usage. When the assistant is unreachable, falls back to the installed CLI
|
|
18
|
+
version and whether the assistant socket is present.
|
|
19
|
+
|
|
20
|
+
With --json, the same fields are emitted as a single JSON object:
|
|
21
|
+
- reachable: whether the assistant answered the health check
|
|
22
|
+
- cliVersion: the installed CLI version
|
|
23
|
+
- assistantVersion: the running assistant version (null when unreachable)
|
|
24
|
+
- versionStale: true when the CLI and assistant versions differ
|
|
25
|
+
- workspace: the workspace directory
|
|
26
|
+
- memory / disk: usage in MB (null when unreachable)
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
$ assistant status
|
|
30
|
+
$ assistant status --json
|
|
31
|
+
$ assistant status --json | jq '.assistantVersion'`,
|
|
8
32
|
};
|
|
@@ -8,6 +8,7 @@ import { getWorkspaceDirDisplay } from "../../util/platform.js";
|
|
|
8
8
|
import { APP_VERSION } from "../../version.js";
|
|
9
9
|
import { applyCommandHelp } from "../lib/cli-command-help.js";
|
|
10
10
|
import { registerCommand } from "../lib/register-command.js";
|
|
11
|
+
import { shouldOutputJson, writeOutput } from "../output.js";
|
|
11
12
|
import { statusHelp } from "./status.help.js";
|
|
12
13
|
|
|
13
14
|
interface HealthResponse {
|
|
@@ -30,6 +31,7 @@ export function registerStatusCommand(program: Command): void {
|
|
|
30
31
|
applyCommandHelp(cmd, statusHelp);
|
|
31
32
|
|
|
32
33
|
cmd.action(async () => {
|
|
34
|
+
const json = shouldOutputJson(cmd);
|
|
33
35
|
const result = await cliIpcCall<HealthResponse>("health");
|
|
34
36
|
|
|
35
37
|
if (!result.ok) {
|
|
@@ -41,6 +43,19 @@ export function registerStatusCommand(program: Command): void {
|
|
|
41
43
|
const socketPath = getAssistantSocketPath();
|
|
42
44
|
const socketExists = existsSync(socketPath);
|
|
43
45
|
const workspace = getWorkspaceDirDisplay();
|
|
46
|
+
if (json) {
|
|
47
|
+
writeOutput(cmd, {
|
|
48
|
+
reachable: false,
|
|
49
|
+
cliVersion: APP_VERSION,
|
|
50
|
+
assistantVersion: null,
|
|
51
|
+
versionStale: false,
|
|
52
|
+
running: socketExists,
|
|
53
|
+
workspace,
|
|
54
|
+
memory: null,
|
|
55
|
+
disk: null,
|
|
56
|
+
});
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
44
59
|
// Daemon unreachable, so its runtime version is unknown; show the
|
|
45
60
|
// installed CLI version so there's always one reliable number.
|
|
46
61
|
process.stdout.write(`CLI Version: ${APP_VERSION}\n`);
|
|
@@ -62,6 +77,20 @@ export function registerStatusCommand(program: Command): void {
|
|
|
62
77
|
const h = result.result;
|
|
63
78
|
const workspace = getWorkspaceDirDisplay();
|
|
64
79
|
|
|
80
|
+
if (json) {
|
|
81
|
+
writeOutput(cmd, {
|
|
82
|
+
reachable: true,
|
|
83
|
+
cliVersion: APP_VERSION,
|
|
84
|
+
assistantVersion: h.version,
|
|
85
|
+
versionStale: h.version !== APP_VERSION,
|
|
86
|
+
running: true,
|
|
87
|
+
workspace,
|
|
88
|
+
memory: h.memory,
|
|
89
|
+
disk: h.disk,
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
65
94
|
// h.version is the running runtime; APP_VERSION is the installed CLI.
|
|
66
95
|
// They drift mid-upgrade (CLI bumped, daemon not yet restarted).
|
|
67
96
|
const runtimeVersion =
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
|
|
5
|
+
import { registerGlobalJsonOption } from "../global-json-option.js";
|
|
6
|
+
|
|
7
|
+
function hasJsonOption(cmd: Command): boolean {
|
|
8
|
+
return cmd.options.some((opt) => opt.long === "--json");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("registerGlobalJsonOption", () => {
|
|
12
|
+
it("adds --json to leaf commands (including nested leaves)", () => {
|
|
13
|
+
const program = new Command();
|
|
14
|
+
program.name("assistant");
|
|
15
|
+
const memory = program.command("memory");
|
|
16
|
+
const items = memory.command("items");
|
|
17
|
+
const list = items.command("list");
|
|
18
|
+
const status = program.command("status");
|
|
19
|
+
|
|
20
|
+
registerGlobalJsonOption(program);
|
|
21
|
+
|
|
22
|
+
expect(hasJsonOption(list)).toBe(true);
|
|
23
|
+
expect(hasJsonOption(status)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("does NOT add --json to group commands (would steal the flag from subcommands)", () => {
|
|
27
|
+
const program = new Command();
|
|
28
|
+
program.name("assistant");
|
|
29
|
+
const clients = program.command("clients");
|
|
30
|
+
const list = clients.command("list");
|
|
31
|
+
|
|
32
|
+
registerGlobalJsonOption(program);
|
|
33
|
+
|
|
34
|
+
// Commander consumes a recognized option at the outermost declaring
|
|
35
|
+
// command, so a group-level --json would swallow `clients list --json`
|
|
36
|
+
// before the leaf action reads it.
|
|
37
|
+
expect(hasJsonOption(clients)).toBe(false);
|
|
38
|
+
expect(hasJsonOption(list)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("keeps `<group> <leaf> --json` bound to the leaf's own opts", () => {
|
|
42
|
+
const program = new Command();
|
|
43
|
+
program.name("assistant").exitOverride();
|
|
44
|
+
const clients = program.command("clients");
|
|
45
|
+
let leafJson: unknown;
|
|
46
|
+
clients.command("list").action((opts: { json?: boolean }) => {
|
|
47
|
+
leafJson = opts.json;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
registerGlobalJsonOption(program);
|
|
51
|
+
|
|
52
|
+
program.parse(["node", "assistant", "clients", "list", "--json"]);
|
|
53
|
+
expect(leafJson).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("does not duplicate --json on a command that already declares it", () => {
|
|
57
|
+
const program = new Command();
|
|
58
|
+
program.name("assistant");
|
|
59
|
+
const sub = program.command("nodes");
|
|
60
|
+
sub.option("--json", "Machine-readable compact JSON output");
|
|
61
|
+
|
|
62
|
+
registerGlobalJsonOption(program);
|
|
63
|
+
|
|
64
|
+
const jsonOptions = sub.options.filter((opt) => opt.long === "--json");
|
|
65
|
+
expect(jsonOptions).toHaveLength(1);
|
|
66
|
+
// The original description is preserved, not overwritten.
|
|
67
|
+
expect(jsonOptions[0]!.description).toBe(
|
|
68
|
+
"Machine-readable compact JSON output",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("leaves the root program without a --json option", () => {
|
|
73
|
+
const program = new Command();
|
|
74
|
+
program.name("assistant");
|
|
75
|
+
program.command("status");
|
|
76
|
+
|
|
77
|
+
registerGlobalJsonOption(program);
|
|
78
|
+
|
|
79
|
+
expect(hasJsonOption(program)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("makes --json parse without error on a command that lacked it", () => {
|
|
83
|
+
const program = new Command();
|
|
84
|
+
program.name("assistant").exitOverride();
|
|
85
|
+
let seenJson: unknown;
|
|
86
|
+
program.command("status").action((opts: { json?: boolean }) => {
|
|
87
|
+
seenJson = opts.json;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
registerGlobalJsonOption(program);
|
|
91
|
+
|
|
92
|
+
expect(() =>
|
|
93
|
+
program.parse(["node", "assistant", "status", "--json"]),
|
|
94
|
+
).not.toThrow();
|
|
95
|
+
expect(seenJson).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -243,3 +243,34 @@ describe("computeContentHash", () => {
|
|
|
243
243
|
expect(second).not.toBe(first);
|
|
244
244
|
});
|
|
245
245
|
});
|
|
246
|
+
|
|
247
|
+
describe("generated app build output is excluded", () => {
|
|
248
|
+
test("apps/<app>/dist is not fingerprinted, so a build is never drift", () => {
|
|
249
|
+
// Installed source: an app with a src/ entry point (no dist yet).
|
|
250
|
+
write("apps/dash/src/main.tsx", "export default 1;");
|
|
251
|
+
write("hooks/stop.ts", "export default () => 1;");
|
|
252
|
+
const baseline = computeFingerprint(root);
|
|
253
|
+
expect(Object.keys(baseline.files)).toContain("apps/dash/src/main.tsx");
|
|
254
|
+
|
|
255
|
+
// The watcher later compiles src -> apps/dash/dist. That generated output
|
|
256
|
+
// must not surface as drift (added files) against the pinned commit.
|
|
257
|
+
write("apps/dash/dist/main.js", "console.log(1)");
|
|
258
|
+
write("apps/dash/dist/index.html", "<html></html>");
|
|
259
|
+
const cmp = compareFingerprint(root, baseline);
|
|
260
|
+
expect(cmp.clean).toBe(true);
|
|
261
|
+
expect(cmp.added).toEqual([]);
|
|
262
|
+
|
|
263
|
+
// A plugin's own top-level dist/ is still tracked (not an app build dir).
|
|
264
|
+
write("dist/bundle.js", "x");
|
|
265
|
+
expect(compareFingerprint(root, baseline).added).toEqual([
|
|
266
|
+
"dist/bundle.js",
|
|
267
|
+
]);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("content hash ignores apps/<app>/dist", () => {
|
|
271
|
+
write("apps/dash/src/main.tsx", "export default 1;");
|
|
272
|
+
const before = computeContentHash(root);
|
|
273
|
+
write("apps/dash/dist/main.js", "console.log(1)");
|
|
274
|
+
expect(computeContentHash(root)).toBe(before);
|
|
275
|
+
});
|
|
276
|
+
});
|