@notis_ai/cli 0.2.0-beta.107.1 → 0.2.0-beta.113.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/scaffolds/notis-affiliate-prospects/CHANGELOG.md +7 -0
- package/dist/scaffolds/notis-affiliate-prospects/README.md +20 -0
- package/dist/scaffolds/notis-affiliate-prospects/app/globals.css +42 -0
- package/dist/scaffolds/notis-affiliate-prospects/app/layout.tsx +7 -0
- package/dist/scaffolds/notis-affiliate-prospects/app/page.tsx +248 -0
- package/dist/scaffolds/notis-affiliate-prospects/app/prospects/page.tsx +134 -0
- package/dist/scaffolds/notis-affiliate-prospects/app/segments/page.tsx +108 -0
- package/dist/scaffolds/notis-affiliate-prospects/components/ui/card.tsx +35 -0
- package/dist/scaffolds/notis-affiliate-prospects/components.json +20 -0
- package/dist/scaffolds/notis-affiliate-prospects/index.html +12 -0
- package/dist/scaffolds/notis-affiliate-prospects/lib/affiliate-data.ts +218 -0
- package/dist/scaffolds/notis-affiliate-prospects/lib/demo-prospects.ts +52 -0
- package/dist/scaffolds/notis-affiliate-prospects/lib/notis-tools.ts +100 -0
- package/dist/scaffolds/notis-affiliate-prospects/lib/utils.ts +25 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-fixtures.json +187 -0
- package/dist/scaffolds/notis-affiliate-prospects/notis.config.ts +65 -0
- package/dist/scaffolds/notis-affiliate-prospects/package.json +32 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/package.json +32 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/components/MultiSelectActionBar.tsx +273 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/config.ts +90 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useDatabase.ts +76 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useMultiSelect.ts +503 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useUpsertDocument.ts +50 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/index.ts +54 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/runtime.ts +161 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/styles.css +38 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-affiliate-prospects/postcss.config.mjs +6 -0
- package/dist/scaffolds/notis-affiliate-prospects/src/dev-main.tsx +72 -0
- package/dist/scaffolds/notis-affiliate-prospects/src/mock-runtime.ts +242 -0
- package/dist/scaffolds/notis-affiliate-prospects/tailwind.config.ts +51 -0
- package/dist/scaffolds/notis-affiliate-prospects/tsconfig.json +23 -0
- package/dist/scaffolds/notis-affiliate-prospects/vite.config.ts +11 -0
- package/dist/scaffolds.json +11 -0
- package/package.json +1 -1
- package/src/cli.js +36 -7
- package/src/command-specs/diagnostics.js +674 -0
- package/src/command-specs/helpers.js +4 -1
- package/src/command-specs/index.js +6 -0
- package/src/command-specs/smoke.js +386 -0
- package/src/command-specs/tools.js +187 -7
- package/src/runtime/output.js +21 -3
- package/src/runtime/profiles.js +198 -13
- package/src/runtime/transport.js +17 -1
package/src/runtime/output.js
CHANGED
|
@@ -22,6 +22,13 @@ function formatTable(rows, columns) {
|
|
|
22
22
|
return [header, divider, ...body].join('\n');
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function formatHint(hint) {
|
|
26
|
+
if (typeof hint?.message === 'string' && hint.message) {
|
|
27
|
+
return ` ${hint.message}`;
|
|
28
|
+
}
|
|
29
|
+
return ` ${hint?.command || ''} ${hint?.reason || ''}`.trimEnd();
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
function yamlScalar(value) {
|
|
26
33
|
if (value === null || value === undefined) {
|
|
27
34
|
return 'null';
|
|
@@ -94,6 +101,14 @@ export class OutputManager {
|
|
|
94
101
|
process.stderr.write(`${message}\n`);
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
emitProgress({ phase, message, requestId = null }) {
|
|
105
|
+
if (this.runtime.quiet) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const suffix = requestId ? ` (${requestId})` : '';
|
|
109
|
+
process.stderr.write(`[${phase}] ${message}${suffix}\n`);
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
emitSuccess({
|
|
98
113
|
ok = true,
|
|
99
114
|
command,
|
|
@@ -116,7 +131,10 @@ export class OutputManager {
|
|
|
116
131
|
meta: {
|
|
117
132
|
profile: this.runtime.profileName,
|
|
118
133
|
api_base: this.runtime.apiBase,
|
|
119
|
-
mutating:
|
|
134
|
+
mutating:
|
|
135
|
+
meta.mutating === true
|
|
136
|
+
? true
|
|
137
|
+
: (meta.mutating === false ? false : null),
|
|
120
138
|
...meta,
|
|
121
139
|
},
|
|
122
140
|
};
|
|
@@ -142,7 +160,7 @@ export class OutputManager {
|
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
if (hints.length) {
|
|
145
|
-
const lines = hints.map(
|
|
163
|
+
const lines = hints.map(formatHint);
|
|
146
164
|
process.stdout.write(`\nNext:\n${lines.join('\n')}\n`);
|
|
147
165
|
}
|
|
148
166
|
|
|
@@ -171,7 +189,7 @@ export class OutputManager {
|
|
|
171
189
|
|
|
172
190
|
process.stderr.write(`Error: ${error.message}\n`);
|
|
173
191
|
for (const hint of error.hints || []) {
|
|
174
|
-
process.stderr.write(
|
|
192
|
+
process.stderr.write(`${formatHint(hint)}\n`);
|
|
175
193
|
}
|
|
176
194
|
return error.exitCode || EXIT_CODES.unexpected;
|
|
177
195
|
}
|
package/src/runtime/profiles.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { dirname, join, parse, resolve } from 'node:path';
|
|
4
4
|
import { CliError, EXIT_CODES } from './errors.js';
|
|
5
5
|
import { getDesktopAuthRecovery } from './desktop-auth.js';
|
|
6
6
|
|
|
@@ -9,6 +9,8 @@ export const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
|
9
9
|
export const WORKSPACE_DIR = join(CONFIG_DIR, 'workspace');
|
|
10
10
|
export const DEFAULT_API_BASE = 'https://api.notis.ai';
|
|
11
11
|
export const DEFAULT_PROFILE = 'default';
|
|
12
|
+
const WORKTREE_RUNTIME_FILENAME = join('.context', 'notis-runtime.json');
|
|
13
|
+
const WORKTREE_ROUTING_FILENAME = join('.context', 'notis-routing.json');
|
|
12
14
|
const LOCAL_DEFAULT_API_BASES = new Set([
|
|
13
15
|
'http://localhost:3001',
|
|
14
16
|
'http://127.0.0.1:3001',
|
|
@@ -77,17 +79,121 @@ export function normalizeConfig(rawConfig = {}) {
|
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
function readJsonFile(path) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function findUp(filename, startDir = process.cwd()) {
|
|
91
|
+
let current = resolve(startDir);
|
|
92
|
+
const root = parse(current).root;
|
|
93
|
+
while (true) {
|
|
94
|
+
const candidate = join(current, filename);
|
|
95
|
+
if (existsSync(candidate)) {
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
if (current === root) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
current = dirname(current);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function processIsAlive(pid) {
|
|
106
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
process.kill(pid, 0);
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resolveWorktreeRuntime(startDir = process.cwd()) {
|
|
118
|
+
if (
|
|
119
|
+
process.env.NODE_ENV === 'test' &&
|
|
120
|
+
process.env.NOTIS_TEST_DISABLE_WORKTREE_ROUTING === '1'
|
|
121
|
+
) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const runtimePath = findUp(WORKTREE_RUNTIME_FILENAME, startDir);
|
|
125
|
+
const routingPath = findUp(WORKTREE_ROUTING_FILENAME, startDir);
|
|
126
|
+
const routing = routingPath ? readJsonFile(routingPath) : null;
|
|
127
|
+
|
|
128
|
+
if (!runtimePath) {
|
|
129
|
+
if (routing?.mode === 'local-only') {
|
|
130
|
+
throw new CliError({
|
|
131
|
+
code: 'dev_runtime_unavailable',
|
|
132
|
+
message: `This worktree is local-only, but its dev.sh runtime is not active`,
|
|
133
|
+
exitCode: EXIT_CODES.network,
|
|
134
|
+
hints: [
|
|
135
|
+
{ message: 'Start ./dev.sh in this worktree, then retry the command.' },
|
|
136
|
+
{ message: `Routing policy: ${routingPath}` },
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const runtime = readJsonFile(runtimePath);
|
|
144
|
+
const apiBase = typeof runtime?.api_base === 'string' ? runtime.api_base.replace(/\/+$/, '') : '';
|
|
145
|
+
const configFile = typeof runtime?.config_file === 'string' ? runtime.config_file : '';
|
|
146
|
+
const pid = Number(runtime?.dev_pid);
|
|
147
|
+
if (
|
|
148
|
+
runtime?.mode !== 'local-only' ||
|
|
149
|
+
!isLocalApiBase(apiBase) ||
|
|
150
|
+
!configFile ||
|
|
151
|
+
!processIsAlive(pid)
|
|
152
|
+
) {
|
|
153
|
+
throw new CliError({
|
|
154
|
+
code: 'dev_runtime_unavailable',
|
|
155
|
+
message: 'The local-only worktree runtime is stale or invalid',
|
|
156
|
+
exitCode: EXIT_CODES.network,
|
|
157
|
+
hints: [
|
|
158
|
+
{ message: 'Restart ./dev.sh in this worktree, then retry the command.' },
|
|
159
|
+
{ message: `Runtime lease: ${runtimePath}` },
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
...runtime,
|
|
166
|
+
api_base: apiBase,
|
|
167
|
+
config_file: resolve(dirname(runtimePath), configFile),
|
|
168
|
+
runtime_path: runtimePath,
|
|
169
|
+
routing_path: routingPath,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function resolveConfigFile(runtime = null) {
|
|
174
|
+
if (runtime?.config_file) {
|
|
175
|
+
return runtime.config_file;
|
|
176
|
+
}
|
|
177
|
+
const envConfigFile = process.env.NOTIS_CLI_CONFIG_FILE;
|
|
178
|
+
if (envConfigFile) {
|
|
179
|
+
return resolve(envConfigFile);
|
|
180
|
+
}
|
|
181
|
+
return CONFIG_FILE;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function loadConfig(runtime = null) {
|
|
185
|
+
const configFile = resolveConfigFile(runtime);
|
|
186
|
+
if (!existsSync(configFile)) {
|
|
82
187
|
return normalizeConfig({});
|
|
83
188
|
}
|
|
84
189
|
|
|
85
|
-
return normalizeConfig(JSON.parse(readFileSync(
|
|
190
|
+
return normalizeConfig(JSON.parse(readFileSync(configFile, 'utf-8')));
|
|
86
191
|
}
|
|
87
192
|
|
|
88
|
-
export function saveConfig(config) {
|
|
89
|
-
|
|
90
|
-
|
|
193
|
+
export function saveConfig(config, runtime = null) {
|
|
194
|
+
const configFile = resolveConfigFile(runtime);
|
|
195
|
+
mkdirSync(dirname(configFile), { recursive: true });
|
|
196
|
+
writeFileSync(configFile, JSON.stringify(normalizeConfig(config), null, 2), { mode: 0o600 });
|
|
91
197
|
}
|
|
92
198
|
|
|
93
199
|
export function getProfile(config, profileName) {
|
|
@@ -194,16 +300,65 @@ export function resolveTimeoutMs(globalOptions = {}) {
|
|
|
194
300
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 30000;
|
|
195
301
|
}
|
|
196
302
|
|
|
197
|
-
export function
|
|
198
|
-
|
|
303
|
+
export function parseDebugEntitlementOverride(value = process.env.NOTIS_DEBUG_ENTITLEMENT_OVERRIDE) {
|
|
304
|
+
if (!value) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
const candidates = [value];
|
|
308
|
+
try {
|
|
309
|
+
candidates.push(Buffer.from(value, 'base64url').toString('utf-8'));
|
|
310
|
+
} catch {
|
|
311
|
+
// The plain JSON candidate below will provide the actionable error.
|
|
312
|
+
}
|
|
313
|
+
for (const candidate of candidates) {
|
|
314
|
+
try {
|
|
315
|
+
const parsed = JSON.parse(candidate);
|
|
316
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
317
|
+
return parsed;
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
// Try the next supported encoding.
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
throw new CliError({
|
|
324
|
+
code: 'debug_entitlement_override_invalid',
|
|
325
|
+
message: 'NOTIS_DEBUG_ENTITLEMENT_OVERRIDE must be a JSON object or base64url-encoded JSON object.',
|
|
326
|
+
exitCode: EXIT_CODES.usage,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function resolveRuntimeProfile(
|
|
331
|
+
globalOptions = {},
|
|
332
|
+
{ requireAuth = true, includeDebugEntitlementOverride = true } = {},
|
|
333
|
+
) {
|
|
334
|
+
const worktreeRuntime = resolveWorktreeRuntime();
|
|
335
|
+
const config = loadConfig(worktreeRuntime);
|
|
199
336
|
const profileName = getCurrentProfileName(config, globalOptions.profile);
|
|
200
|
-
const
|
|
201
|
-
|
|
337
|
+
const requestedApiBase = globalOptions.apiBase;
|
|
338
|
+
if (
|
|
339
|
+
worktreeRuntime &&
|
|
340
|
+
requestedApiBase &&
|
|
341
|
+
requestedApiBase.replace(/\/+$/, '') !== worktreeRuntime.api_base
|
|
342
|
+
) {
|
|
343
|
+
throw new CliError({
|
|
344
|
+
code: 'dev_runtime_route_mismatch',
|
|
345
|
+
message: `This local-only worktree cannot route to ${requestedApiBase}`,
|
|
346
|
+
exitCode: EXIT_CODES.usage,
|
|
347
|
+
hints: [{ message: `Expected local API: ${worktreeRuntime.api_base}` }],
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
const apiBase = worktreeRuntime
|
|
351
|
+
? worktreeRuntime.api_base
|
|
352
|
+
: getApiBase(config, profileName, globalOptions.apiBase);
|
|
353
|
+
const jwt = worktreeRuntime ? getProfile(config, profileName).jwt : getJwt(config, profileName);
|
|
202
354
|
const profile = getProfile(config, profileName);
|
|
203
355
|
const agentMode = isAgentMode(globalOptions);
|
|
204
356
|
const nonInteractive = isNonInteractive(globalOptions);
|
|
205
357
|
const outputMode = resolveOutputMode(globalOptions);
|
|
206
358
|
const timeoutMs = resolveTimeoutMs(globalOptions);
|
|
359
|
+
const debugEntitlementOverride = includeDebugEntitlementOverride
|
|
360
|
+
? parseDebugEntitlementOverride()
|
|
361
|
+
: null;
|
|
207
362
|
|
|
208
363
|
if (requireAuth && !jwt) {
|
|
209
364
|
const recovery = getDesktopAuthRecovery({
|
|
@@ -218,22 +373,38 @@ export function resolveRuntimeProfile(globalOptions = {}, { requireAuth = true }
|
|
|
218
373
|
hints: recovery.hints,
|
|
219
374
|
});
|
|
220
375
|
}
|
|
376
|
+
if (
|
|
377
|
+
worktreeRuntime?.expected_user_id &&
|
|
378
|
+
getJwtSubject(jwt) !== worktreeRuntime.expected_user_id
|
|
379
|
+
) {
|
|
380
|
+
throw new CliError({
|
|
381
|
+
code: 'dev_runtime_identity_mismatch',
|
|
382
|
+
message: 'The scoped dev credential does not belong to this worktree test user',
|
|
383
|
+
exitCode: EXIT_CODES.auth,
|
|
384
|
+
hints: [
|
|
385
|
+
{ message: 'Restart ./dev.sh to restore the approved worktree identity.' },
|
|
386
|
+
{ message: `Expected user: ${worktreeRuntime.expected_user_id}` },
|
|
387
|
+
],
|
|
388
|
+
});
|
|
389
|
+
}
|
|
221
390
|
|
|
222
391
|
// An explicit NOTIS_JWT is a complete credential override. Use it verbatim
|
|
223
392
|
// and never replace it with a token later synced by the desktop profile.
|
|
224
|
-
const usingEnvJwt = Boolean(process.env.NOTIS_JWT);
|
|
393
|
+
const usingEnvJwt = !worktreeRuntime && Boolean(process.env.NOTIS_JWT);
|
|
225
394
|
return {
|
|
226
395
|
config,
|
|
227
396
|
profileName,
|
|
228
397
|
apiBase,
|
|
229
398
|
jwt,
|
|
230
|
-
credentialSource: usingEnvJwt ? 'env' : 'profile',
|
|
399
|
+
credentialSource: worktreeRuntime ? 'worktree' : usingEnvJwt ? 'env' : 'profile',
|
|
231
400
|
desktopAppName: usingEnvJwt ? undefined : profile.desktop_app_name,
|
|
232
401
|
desktopPid: usingEnvJwt ? undefined : profile.desktop_pid,
|
|
233
402
|
agentMode,
|
|
234
403
|
nonInteractive,
|
|
235
404
|
outputMode,
|
|
236
405
|
timeoutMs,
|
|
406
|
+
debugEntitlementOverride,
|
|
407
|
+
worktreeRuntime,
|
|
237
408
|
};
|
|
238
409
|
}
|
|
239
410
|
|
|
@@ -251,6 +422,20 @@ export function getJwtExpiration(jwt) {
|
|
|
251
422
|
}
|
|
252
423
|
}
|
|
253
424
|
|
|
425
|
+
export function getJwtSubject(jwt) {
|
|
426
|
+
if (typeof jwt !== 'string' || !jwt) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
const parts = jwt.split('.');
|
|
431
|
+
if (parts.length !== 3) return null;
|
|
432
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
|
|
433
|
+
return typeof payload.sub === 'string' && payload.sub ? payload.sub : null;
|
|
434
|
+
} catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
254
439
|
export function isJwtExpired(jwt, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
255
440
|
const expiration = getJwtExpiration(jwt);
|
|
256
441
|
return expiration !== null && expiration <= nowSeconds;
|
package/src/runtime/transport.js
CHANGED
|
@@ -4,6 +4,7 @@ import { CliError, EXIT_CODES } from './errors.js';
|
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_PROFILE,
|
|
6
6
|
getProfile,
|
|
7
|
+
getJwtSubject,
|
|
7
8
|
isJwtExpired,
|
|
8
9
|
loadConfig,
|
|
9
10
|
} from './profiles.js';
|
|
@@ -149,7 +150,7 @@ function reloadJwtFromConfig(runtime) {
|
|
|
149
150
|
const profileName = runtime.profileName || DEFAULT_PROFILE;
|
|
150
151
|
let profile;
|
|
151
152
|
try {
|
|
152
|
-
profile = getProfile(loadConfig(), profileName);
|
|
153
|
+
profile = getProfile(loadConfig(runtime.worktreeRuntime), profileName);
|
|
153
154
|
} catch {
|
|
154
155
|
return false;
|
|
155
156
|
}
|
|
@@ -157,6 +158,18 @@ function reloadJwtFromConfig(runtime) {
|
|
|
157
158
|
if (!nextJwt || nextJwt === runtime.jwt) {
|
|
158
159
|
return false;
|
|
159
160
|
}
|
|
161
|
+
const expectedUserId = runtime.worktreeRuntime?.expected_user_id;
|
|
162
|
+
if (expectedUserId && getJwtSubject(nextJwt) !== expectedUserId) {
|
|
163
|
+
throw new CliError({
|
|
164
|
+
code: 'dev_runtime_identity_mismatch',
|
|
165
|
+
message: 'The refreshed scoped dev credential does not belong to this worktree test user',
|
|
166
|
+
exitCode: EXIT_CODES.auth,
|
|
167
|
+
hints: [
|
|
168
|
+
{ message: 'Restart ./dev.sh to restore the approved worktree identity.' },
|
|
169
|
+
{ message: `Expected user: ${expectedUserId}` },
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
160
173
|
runtime.jwt = nextJwt;
|
|
161
174
|
runtime.desktopAppName = profile.desktop_app_name;
|
|
162
175
|
runtime.desktopPid = profile.desktop_pid;
|
|
@@ -318,6 +331,9 @@ export async function callTool({
|
|
|
318
331
|
cwd: process.cwd(),
|
|
319
332
|
agent_mode: runtime.agentMode,
|
|
320
333
|
cli_version: runtime.cliVersion,
|
|
334
|
+
...(runtime.debugEntitlementOverride
|
|
335
|
+
? { debug_entitlement_override: runtime.debugEntitlementOverride }
|
|
336
|
+
: {}),
|
|
321
337
|
},
|
|
322
338
|
};
|
|
323
339
|
|