@vibecompass/vibecompass 0.13.1 → 0.15.0
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/CHANGELOG.md +96 -1
- package/README.md +51 -9
- package/package.json +1 -1
- package/src/cli.js +611 -14
- package/src/credential-store.js +856 -0
- package/src/docs-review.js +28 -4
- package/src/generators/agent-files/index.js +11 -3
- package/src/generators/agent-files/template.js +1 -0
- package/src/index.js +12 -0
- package/src/login.js +257 -0
- package/src/mode-transition.js +8 -2
- package/src/scaffold.js +15 -4
- package/src/secret-prompt.js +64 -0
- package/src/session.js +456 -5
- package/src/status.js +54 -9
- package/src/sync.js +12 -4
- package/src/workflows/registry.js +2 -1
package/src/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { planDocsUpdate, renderDocsUpdatePlan } from './docs-update.js';
|
|
|
8
8
|
import { resolveConnectHostedCliOptions, resolveInitCliOptions } from './setup.js';
|
|
9
9
|
import { readFile } from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { closeProjectSession, listProjectSessions, readLaneEnvironment, rebuildActiveSessionIndex, startProjectSession, switchProjectSession, writeLaneMarkerForSession } from './session.js';
|
|
11
|
+
import { closeProjectSession, continueProjectSession, listProjectSessions, readLaneEnvironment, rebuildActiveSessionIndex, renderLaneInventoryLines, startProjectSession, switchProjectSession, writeLaneMarkerForSession } from './session.js';
|
|
12
12
|
import { appendDecisionEntry, formatDecisionId, readNextDecisionId } from './decisions.js';
|
|
13
13
|
import { checkGroupedDecisionIndex, refreshGroupedDecisionIndex, resolveDefaultIndexGroupLabel } from './decision-index.js';
|
|
14
14
|
import { resolveLaneMarkerContext, resolveLaneSelection } from './lane-marker.js';
|
|
@@ -18,6 +18,17 @@ import { refreshWorkflow } from './refresh-workflow.js';
|
|
|
18
18
|
import { inspectProjectCompatibility, formatCompatibilityWarnings } from './compatibility.js';
|
|
19
19
|
import { PACKAGE_VERSION } from './version.js';
|
|
20
20
|
import { demoteHosted, promoteHosted } from './mode-transition.js';
|
|
21
|
+
import { loginHosted } from './login.js';
|
|
22
|
+
import {
|
|
23
|
+
credentialStoreKey,
|
|
24
|
+
credentialStoreOptionsFrom,
|
|
25
|
+
listSyncCredentials,
|
|
26
|
+
removeSyncCredential,
|
|
27
|
+
storeSyncCredential,
|
|
28
|
+
} from './credential-store.js';
|
|
29
|
+
import { canPromptSecret, promptSecret, readSecretFromStdin } from './secret-prompt.js';
|
|
30
|
+
import { resolveSyncBinding, readSyncTargets } from './sync-binding.js';
|
|
31
|
+
import { parseSimpleYaml } from './simple-yaml.js';
|
|
21
32
|
import {
|
|
22
33
|
adoptRemoteHead,
|
|
23
34
|
applyPullExport,
|
|
@@ -71,7 +82,7 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
|
|
|
71
82
|
if (result.syncEnvVar) {
|
|
72
83
|
const rootFlag = formatOptionalRootFlag(initPlan.initOptions.rootDir);
|
|
73
84
|
io.stdout.write(`Hosted binding: configured for ${initPlan.initOptions.mode}\n`);
|
|
74
|
-
io.stdout.write(`Next step:
|
|
85
|
+
io.stdout.write(`Next step: sign in with "vibecompass login${rootFlag}" (opens the dashboard and stores the sync token locally).\n`);
|
|
75
86
|
if (initPlan.initOptions.mode === 'local-primary') {
|
|
76
87
|
io.stdout.write(`Then run: vibecompass push${rootFlag}\n`);
|
|
77
88
|
io.stdout.write(`Hosted docs-review: vibecompass docs-review --submit-hosted${rootFlag}\n`);
|
|
@@ -204,7 +215,22 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
|
|
|
204
215
|
io.stdout.write(`Sync target: ${result.syncTarget.name} (default: ${result.syncTarget.defaultTarget})\n`);
|
|
205
216
|
io.stdout.write(`Select per command with --sync-target ${result.syncTarget.name}, or switch the default with: vibecompass sync-target ${result.syncTarget.name}\n`);
|
|
206
217
|
}
|
|
207
|
-
|
|
218
|
+
const capture = await captureSyncTokenAfterConnect(io, runtime, {
|
|
219
|
+
binding: {
|
|
220
|
+
apiUrl: connectPlan.sync.apiUrl,
|
|
221
|
+
projectId: connectPlan.sync.projectId,
|
|
222
|
+
credentialEnvVar: result.syncEnvVar,
|
|
223
|
+
target: result.syncTarget?.name ?? null,
|
|
224
|
+
},
|
|
225
|
+
rootDir: parsed.options.rootDir,
|
|
226
|
+
tokenStdin: Boolean(parsed.options.tokenStdin),
|
|
227
|
+
noStore: Boolean(parsed.options.noStore),
|
|
228
|
+
credentialStore: parsed.options.credentialStore,
|
|
229
|
+
rootFlag: formatOptionalRootFlag(parsed.options.rootDir),
|
|
230
|
+
});
|
|
231
|
+
if (capture?.viaLogin) {
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
208
234
|
if (result.mode === 'local-primary') {
|
|
209
235
|
io.stdout.write('Then run: vibecompass push\n');
|
|
210
236
|
io.stdout.write('Hosted docs-review: vibecompass docs-review --submit-hosted\n');
|
|
@@ -214,6 +240,22 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
|
|
|
214
240
|
return 0;
|
|
215
241
|
}
|
|
216
242
|
|
|
243
|
+
if (parsed.command === 'login') {
|
|
244
|
+
await writeCompatibilityPreflightWarnings(io, parsed.options, runtime);
|
|
245
|
+
const result = await loginHosted(parsed.options, {
|
|
246
|
+
cwd: runtime.cwd,
|
|
247
|
+
env: runtime.env,
|
|
248
|
+
io,
|
|
249
|
+
runtime,
|
|
250
|
+
});
|
|
251
|
+
writeLoginResult(io, result);
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (parsed.command === 'sync-credential') {
|
|
256
|
+
return runSyncCredentialCommand(io, runtime, parsed.options);
|
|
257
|
+
}
|
|
258
|
+
|
|
217
259
|
if (parsed.command === 'sync-target') {
|
|
218
260
|
const result = await setDefaultSyncTarget({
|
|
219
261
|
...parsed.options,
|
|
@@ -298,20 +340,66 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
|
|
|
298
340
|
return 0;
|
|
299
341
|
}
|
|
300
342
|
|
|
343
|
+
if (parsed.command === 'continue-session') {
|
|
344
|
+
await writeCompatibilityPreflightWarnings(io, parsed.options, runtime);
|
|
345
|
+
const result = await continueProjectSession({
|
|
346
|
+
...parsed.options,
|
|
347
|
+
...(runtime.cwd ? { cwd: runtime.cwd } : {}),
|
|
348
|
+
});
|
|
349
|
+
if (parsed.options.json) {
|
|
350
|
+
const { manifest: _manifest, docsUpdatePlan: _plan, agentFileSync, ...json } = result;
|
|
351
|
+
io.stdout.write(`${JSON.stringify({ ...json, agentFileSync: agentFileSync?.results ?? [] }, null, 2)}\n`);
|
|
352
|
+
writeWarnings(io, result.warnings);
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
{
|
|
356
|
+
// Text mode.
|
|
357
|
+
io.stdout.write(`Continued lane ${result.sessionId} (session ${result.sessionDate}-${result.sessionNumber}, resume #${result.resumeCount}${result.laneAgeDays != null ? `, opened ${result.laneAgeDays} day${result.laneAgeDays === 1 ? '' : 's'} ago` : ''}; selected via ${result.selectionSource})\n`);
|
|
358
|
+
io.stdout.write(`Working on: ${result.workingOn}${result.workingOnUpdated ? ' (updated)' : ''}\n`);
|
|
359
|
+
io.stdout.write(`Updated ${result.claudePath}\n`);
|
|
360
|
+
io.stdout.write('Read next:\n');
|
|
361
|
+
io.stdout.write(`- ${result.wipFilePath}\n`);
|
|
362
|
+
io.stdout.write(`- ${result.handoffFilePath}\n`);
|
|
363
|
+
if (result.latestFinalizedSessionPath) {
|
|
364
|
+
io.stdout.write(`- ${result.latestFinalizedSessionPath} (latest finalized note)\n`);
|
|
365
|
+
}
|
|
366
|
+
if (result.gitBinding) {
|
|
367
|
+
const worktreeRepos = Object.keys(result.gitBinding.worktrees);
|
|
368
|
+
io.stdout.write(`Git binding: branch "${result.gitBinding.branch}"${worktreeRepos.length > 0 ? ` with worktrees for ${worktreeRepos.join(', ')} under ${result.gitBinding.worktreeContainer}` : ' (no worktrees)'}\n`);
|
|
369
|
+
}
|
|
370
|
+
if (result.runtime) {
|
|
371
|
+
io.stdout.write(`Runtime: port ${result.runtime.port}, temp dir ${result.runtime.tmpDir}\n`);
|
|
372
|
+
io.stdout.write(`Export into a shell with: eval "$(vibecompass lane-env${result.otherLanes.length > 0 ? ` --session ${result.sessionId}` : ''})" (D-282).\n`);
|
|
373
|
+
}
|
|
374
|
+
if (result.otherLanes.length > 0) {
|
|
375
|
+
io.stdout.write(`Other active lanes (${result.otherLanes.length}) — pass --session ${result.sessionId} to lane-scoped commands, or run them from this lane's worktree:\n`);
|
|
376
|
+
for (const lane of result.otherLanes) {
|
|
377
|
+
io.stdout.write(`- ${lane.id}: ${lane.workingOn ?? 'No summary recorded'}\n`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
writeWarnings(io, result.warnings);
|
|
382
|
+
writeAgentFileSyncResult(io, result.agentFileSync);
|
|
383
|
+
return 0;
|
|
384
|
+
}
|
|
385
|
+
|
|
301
386
|
if (parsed.command === 'list-sessions') {
|
|
302
387
|
await writeCompatibilityPreflightWarnings(io, parsed.options, runtime);
|
|
303
388
|
const result = await listProjectSessions({
|
|
304
389
|
...parsed.options,
|
|
305
390
|
...(runtime.cwd ? { cwd: runtime.cwd } : {}),
|
|
306
391
|
});
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
392
|
+
if (parsed.options.json) {
|
|
393
|
+
io.stdout.write(`${JSON.stringify({ root_dir: result.rootDir, ...result.inventory }, null, 2)}\n`);
|
|
394
|
+
return 0;
|
|
395
|
+
}
|
|
396
|
+
for (const line of renderLaneInventoryLines(result.inventory)) {
|
|
397
|
+
io.stdout.write(`${line}\n`);
|
|
398
|
+
}
|
|
399
|
+
if (result.inventory.lanes.length > 1) {
|
|
400
|
+
io.stdout.write('Multiple lanes are active: resume one with `vibecompass continue-session <lane-id>`; the current hint is not a selection (D-277/D-353).\n');
|
|
401
|
+
} else if (result.inventory.lanes.length === 1) {
|
|
402
|
+
io.stdout.write(`Resume with \`vibecompass continue-session\` (single active lane resolves implicitly; D-353).\n`);
|
|
315
403
|
}
|
|
316
404
|
return 0;
|
|
317
405
|
}
|
|
@@ -716,6 +804,14 @@ export function parseCliArgs(argv) {
|
|
|
716
804
|
return parseSyncTargetArgs(rest);
|
|
717
805
|
}
|
|
718
806
|
|
|
807
|
+
if (command === 'login') {
|
|
808
|
+
return parseLoginArgs(rest);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (command === 'sync-credential') {
|
|
812
|
+
return parseSyncCredentialArgs(rest);
|
|
813
|
+
}
|
|
814
|
+
|
|
719
815
|
if (command === 'status') {
|
|
720
816
|
return parseStatusArgs(rest);
|
|
721
817
|
}
|
|
@@ -736,6 +832,10 @@ export function parseCliArgs(argv) {
|
|
|
736
832
|
return parseListSessionsArgs(rest);
|
|
737
833
|
}
|
|
738
834
|
|
|
835
|
+
if (command === 'continue-session') {
|
|
836
|
+
return parseContinueSessionArgs(rest);
|
|
837
|
+
}
|
|
838
|
+
|
|
739
839
|
if (command === 'switch-session') {
|
|
740
840
|
return parseSwitchSessionArgs(rest);
|
|
741
841
|
}
|
|
@@ -1112,6 +1212,16 @@ function parseConnectHostedArgs(argv) {
|
|
|
1112
1212
|
for (let index = 0; index < argv.length; index += 1) {
|
|
1113
1213
|
const token = argv[index];
|
|
1114
1214
|
|
|
1215
|
+
if (token === '--token-stdin') {
|
|
1216
|
+
parsed.tokenStdin = true;
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (token === '--no-store') {
|
|
1221
|
+
parsed.noStore = true;
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1115
1225
|
if (!token.startsWith('--')) {
|
|
1116
1226
|
throw new Error(`Unexpected argument "${token}".`);
|
|
1117
1227
|
}
|
|
@@ -1138,6 +1248,9 @@ function parseConnectHostedArgs(argv) {
|
|
|
1138
1248
|
case '--sync-credential-env-var':
|
|
1139
1249
|
parsed.syncCredentialEnvVar = value;
|
|
1140
1250
|
break;
|
|
1251
|
+
case '--credential-store':
|
|
1252
|
+
parsed.credentialStore = value;
|
|
1253
|
+
break;
|
|
1141
1254
|
default:
|
|
1142
1255
|
throw new Error(`Unknown flag "${token}".`);
|
|
1143
1256
|
}
|
|
@@ -1153,6 +1266,9 @@ function parseConnectHostedArgs(argv) {
|
|
|
1153
1266
|
command: 'connect-hosted',
|
|
1154
1267
|
options: {
|
|
1155
1268
|
rootDir: parsed.rootDir,
|
|
1269
|
+
...(parsed.tokenStdin ? { tokenStdin: true } : {}),
|
|
1270
|
+
...(parsed.noStore ? { noStore: true } : {}),
|
|
1271
|
+
...(parsed.credentialStore ? { credentialStore: parsed.credentialStore } : {}),
|
|
1156
1272
|
...(parsed.targetName ? { targetName: parsed.targetName } : {}),
|
|
1157
1273
|
...(syncValues.length > 0
|
|
1158
1274
|
? {
|
|
@@ -1167,6 +1283,363 @@ function parseConnectHostedArgs(argv) {
|
|
|
1167
1283
|
};
|
|
1168
1284
|
}
|
|
1169
1285
|
|
|
1286
|
+
function parseLoginArgs(argv) {
|
|
1287
|
+
const parsed = {};
|
|
1288
|
+
|
|
1289
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1290
|
+
const token = argv[index];
|
|
1291
|
+
|
|
1292
|
+
if (token === '--no-browser') {
|
|
1293
|
+
parsed.noBrowser = true;
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if (!token.startsWith('--')) {
|
|
1298
|
+
throw new Error(`Unexpected argument "${token}".`);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
const value = argv[index + 1];
|
|
1302
|
+
if (value === undefined) {
|
|
1303
|
+
throw new Error(`Flag "${token}" requires a value.`);
|
|
1304
|
+
}
|
|
1305
|
+
index += 1;
|
|
1306
|
+
|
|
1307
|
+
switch (token) {
|
|
1308
|
+
case '--root':
|
|
1309
|
+
parsed.rootDir = value;
|
|
1310
|
+
break;
|
|
1311
|
+
case '--sync-target':
|
|
1312
|
+
parsed.syncTarget = value;
|
|
1313
|
+
break;
|
|
1314
|
+
case '--api-url':
|
|
1315
|
+
parsed.apiUrl = value;
|
|
1316
|
+
break;
|
|
1317
|
+
case '--project-id':
|
|
1318
|
+
parsed.projectId = value;
|
|
1319
|
+
break;
|
|
1320
|
+
case '--sync-credential-env-var':
|
|
1321
|
+
parsed.credentialEnvVar = value;
|
|
1322
|
+
break;
|
|
1323
|
+
case '--label':
|
|
1324
|
+
parsed.label = value;
|
|
1325
|
+
break;
|
|
1326
|
+
case '--credential-store':
|
|
1327
|
+
parsed.credentialStore = value;
|
|
1328
|
+
break;
|
|
1329
|
+
case '--poll-timeout': {
|
|
1330
|
+
const seconds = Number(value);
|
|
1331
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
1332
|
+
throw new Error('--poll-timeout requires a positive number of seconds.');
|
|
1333
|
+
}
|
|
1334
|
+
parsed.pollTimeoutSeconds = seconds;
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
default:
|
|
1338
|
+
throw new Error(`Unknown flag "${token}" for login.`);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
return { command: 'login', options: parsed };
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
const SYNC_CREDENTIAL_ACTIONS = new Set(['set', 'remove', 'list']);
|
|
1346
|
+
|
|
1347
|
+
function parseSyncCredentialArgs(argv) {
|
|
1348
|
+
const parsed = { action: null };
|
|
1349
|
+
|
|
1350
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1351
|
+
const token = argv[index];
|
|
1352
|
+
|
|
1353
|
+
if (token === '--token-stdin') {
|
|
1354
|
+
parsed.tokenStdin = true;
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
if (token === '--from-env') {
|
|
1358
|
+
parsed.fromEnv = true;
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
if (token === '--json') {
|
|
1362
|
+
parsed.json = true;
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
if (!token.startsWith('--')) {
|
|
1367
|
+
if (parsed.action !== null) {
|
|
1368
|
+
throw new Error(`Unexpected argument "${token}".`);
|
|
1369
|
+
}
|
|
1370
|
+
if (!SYNC_CREDENTIAL_ACTIONS.has(token)) {
|
|
1371
|
+
throw new Error(`Unknown sync-credential action "${token}". Use set, remove, or list.`);
|
|
1372
|
+
}
|
|
1373
|
+
parsed.action = token;
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const value = argv[index + 1];
|
|
1378
|
+
if (value === undefined) {
|
|
1379
|
+
throw new Error(`Flag "${token}" requires a value.`);
|
|
1380
|
+
}
|
|
1381
|
+
index += 1;
|
|
1382
|
+
|
|
1383
|
+
switch (token) {
|
|
1384
|
+
case '--root':
|
|
1385
|
+
parsed.rootDir = value;
|
|
1386
|
+
break;
|
|
1387
|
+
case '--sync-target':
|
|
1388
|
+
parsed.syncTarget = value;
|
|
1389
|
+
break;
|
|
1390
|
+
case '--api-url':
|
|
1391
|
+
parsed.apiUrl = value;
|
|
1392
|
+
break;
|
|
1393
|
+
case '--project-id':
|
|
1394
|
+
parsed.projectId = value;
|
|
1395
|
+
break;
|
|
1396
|
+
case '--label':
|
|
1397
|
+
parsed.label = value;
|
|
1398
|
+
break;
|
|
1399
|
+
case '--credential-store':
|
|
1400
|
+
parsed.credentialStore = value;
|
|
1401
|
+
break;
|
|
1402
|
+
default:
|
|
1403
|
+
throw new Error(`Unknown flag "${token}" for sync-credential.`);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
if (!parsed.action) {
|
|
1408
|
+
throw new Error('sync-credential requires an action: set, remove, or list.');
|
|
1409
|
+
}
|
|
1410
|
+
if ((parsed.apiUrl && !parsed.projectId) || (!parsed.apiUrl && parsed.projectId)) {
|
|
1411
|
+
throw new Error('--api-url and --project-id must be given together.');
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
return { command: 'sync-credential', options: parsed };
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
async function readRootSyncBindings(cwd, rootDirOption) {
|
|
1418
|
+
const rootDir = path.resolve(cwd, rootDirOption ?? '.compass');
|
|
1419
|
+
const projectFilePath = path.join(rootDir, 'project.yaml');
|
|
1420
|
+
let project;
|
|
1421
|
+
try {
|
|
1422
|
+
project = parseSimpleYaml(await readFile(projectFilePath, 'utf8'), { sourceName: projectFilePath });
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
if (error && typeof error === 'object' && error.code === 'ENOENT') {
|
|
1425
|
+
return { rootDir, project: null, bindings: [] };
|
|
1426
|
+
}
|
|
1427
|
+
throw error;
|
|
1428
|
+
}
|
|
1429
|
+
const named = readSyncTargets(project);
|
|
1430
|
+
const bindings = [];
|
|
1431
|
+
if (named) {
|
|
1432
|
+
for (const name of Object.keys(named.targets)) {
|
|
1433
|
+
try {
|
|
1434
|
+
bindings.push(resolveSyncBinding(project, name));
|
|
1435
|
+
} catch {
|
|
1436
|
+
// incomplete target; skip in listings
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
} else {
|
|
1440
|
+
const flat = resolveSyncBinding(project, null);
|
|
1441
|
+
if (flat) bindings.push(flat);
|
|
1442
|
+
}
|
|
1443
|
+
return { rootDir, project, bindings };
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
async function resolveSyncCredentialBinding(runtime, options) {
|
|
1447
|
+
if (options.apiUrl && options.projectId) {
|
|
1448
|
+
return {
|
|
1449
|
+
target: null,
|
|
1450
|
+
apiUrl: options.apiUrl,
|
|
1451
|
+
projectId: options.projectId,
|
|
1452
|
+
credentialEnvVar: 'VIBECOMPASS_SYNC_TOKEN',
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
const cwd = runtime.cwd ? path.resolve(runtime.cwd) : process.cwd();
|
|
1456
|
+
const { rootDir, project } = await readRootSyncBindings(cwd, options.rootDir);
|
|
1457
|
+
if (!project) {
|
|
1458
|
+
throw new Error(`No project.yaml found in ${rootDir}. Run "vibecompass login" from an initialized root, or pass --api-url and --project-id.`);
|
|
1459
|
+
}
|
|
1460
|
+
const binding = resolveSyncBinding(project, options.syncTarget ?? null);
|
|
1461
|
+
if (!binding) {
|
|
1462
|
+
throw new Error('This root has no hosted sync binding yet. Run "vibecompass login" (recommended) or "vibecompass connect-hosted", or pass --api-url and --project-id.');
|
|
1463
|
+
}
|
|
1464
|
+
return binding;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function writeLoginResult(io, result) {
|
|
1468
|
+
io.stdout.write(`Signed in to ${result.apiUrl} for project ${result.project.name} (${result.project.id}).\n`);
|
|
1469
|
+
writeStoredCredentialLines(io, result.stored, {
|
|
1470
|
+
apiUrl: result.apiUrl,
|
|
1471
|
+
projectId: result.project.id,
|
|
1472
|
+
credentialEnvVar: result.binding?.syncEnvVar ?? result.previousBinding?.credentialEnvVar ?? 'VIBECOMPASS_SYNC_TOKEN',
|
|
1473
|
+
});
|
|
1474
|
+
if (result.binding) {
|
|
1475
|
+
io.stdout.write(`Connected hosted VibeCompass for ${result.binding.mode}\n`);
|
|
1476
|
+
if (result.binding.modeChanged) {
|
|
1477
|
+
io.stdout.write(`Project mode: ${result.binding.previousMode} -> ${result.binding.mode}\n`);
|
|
1478
|
+
}
|
|
1479
|
+
io.stdout.write(`Updated ${result.binding.projectFilePath}\n`);
|
|
1480
|
+
if (result.binding.syncTarget) {
|
|
1481
|
+
io.stdout.write(`Sync target: ${result.binding.syncTarget.name} (default: ${result.binding.syncTarget.defaultTarget})\n`);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
for (const warning of result.warnings) {
|
|
1485
|
+
io.stdout.write(`WARNING: ${warning}\n`);
|
|
1486
|
+
}
|
|
1487
|
+
const targetFlag = result.previousBinding?.target ?? result.binding?.syncTarget?.name;
|
|
1488
|
+
const targetSuffix = targetFlag ? ` --sync-target ${targetFlag}` : '';
|
|
1489
|
+
if (result.mode === 'local-primary') {
|
|
1490
|
+
io.stdout.write(`Then run: vibecompass push${targetSuffix}\n`);
|
|
1491
|
+
}
|
|
1492
|
+
io.stdout.write(`Hosted docs-review: vibecompass docs-review --submit-hosted${targetSuffix}\n`);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function writeStoredCredentialLines(io, stored, binding) {
|
|
1496
|
+
for (const warning of stored.warnings ?? []) {
|
|
1497
|
+
io.stdout.write(`WARNING: ${warning}\n`);
|
|
1498
|
+
}
|
|
1499
|
+
const where = stored.backend === 'keychain' ? `the ${stored.backendDescription}` : stored.storePath;
|
|
1500
|
+
io.stdout.write(`Stored the sync token for ${binding.apiUrl} (project ${binding.projectId}) in ${where}${stored.replaced ? ', replacing the previous entry' : ''}.\n`);
|
|
1501
|
+
io.stdout.write(`Hosted commands find it automatically in every terminal; ${binding.credentialEnvVar} still overrides it per shell.\n`);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
async function captureSyncTokenAfterConnect(io, runtime, spec) {
|
|
1505
|
+
const { binding } = spec;
|
|
1506
|
+
const env = runtime.env ?? process.env;
|
|
1507
|
+
const targetFlag = binding.target ? ` --sync-target ${binding.target}` : '';
|
|
1508
|
+
const loginHint = `vibecompass login${spec.rootFlag}${targetFlag}`;
|
|
1509
|
+
|
|
1510
|
+
if (spec.noStore) {
|
|
1511
|
+
io.stdout.write(`Token not stored (--no-store). Set ${binding.credentialEnvVar} in every shell that runs hosted commands, or later run: ${loginHint}\n`);
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
let token = null;
|
|
1516
|
+
if (spec.tokenStdin) {
|
|
1517
|
+
token = await readSecretFromStdin(io, runtime);
|
|
1518
|
+
if (!token) {
|
|
1519
|
+
throw new Error('--token-stdin was given but no token arrived on stdin.');
|
|
1520
|
+
}
|
|
1521
|
+
} else if (typeof env[binding.credentialEnvVar] === 'string' && env[binding.credentialEnvVar].trim() !== '') {
|
|
1522
|
+
io.stdout.write(`${binding.credentialEnvVar} is set in this shell and takes precedence for hosted commands. Persist it for new terminals with: vibecompass sync-credential set${spec.rootFlag}${targetFlag} --from-env\n`);
|
|
1523
|
+
return null;
|
|
1524
|
+
} else if (canPromptSecret(io, runtime)) {
|
|
1525
|
+
// D-356: the recommended path is the browser sign-in. A pasted dashboard
|
|
1526
|
+
// token is the advanced alternative; Enter falls through to login.
|
|
1527
|
+
token = await promptSecret('Press Enter to sign in with your browser, or paste a dashboard token (input hidden)', io, runtime);
|
|
1528
|
+
if (!token) {
|
|
1529
|
+
const result = await loginHosted(
|
|
1530
|
+
{
|
|
1531
|
+
rootDir: spec.rootDir,
|
|
1532
|
+
...(binding.target ? { syncTarget: binding.target } : {}),
|
|
1533
|
+
...(spec.credentialStore ? { credentialStore: spec.credentialStore } : {}),
|
|
1534
|
+
},
|
|
1535
|
+
{ cwd: runtime.cwd, env, io, runtime },
|
|
1536
|
+
);
|
|
1537
|
+
writeLoginResult(io, result);
|
|
1538
|
+
return { viaLogin: true, stored: result.stored };
|
|
1539
|
+
}
|
|
1540
|
+
} else {
|
|
1541
|
+
io.stdout.write(`Next step: sign in with "${loginHint}" (opens the dashboard and stores the sync token locally). CI or scripts: set ${binding.credentialEnvVar}, or pipe a dashboard token into "vibecompass connect-hosted ... --token-stdin".\n`);
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
const stored = await storeSyncCredential(
|
|
1546
|
+
{
|
|
1547
|
+
apiUrl: binding.apiUrl,
|
|
1548
|
+
projectId: binding.projectId,
|
|
1549
|
+
token,
|
|
1550
|
+
label: `VibeCompass sync token (${binding.projectId})`,
|
|
1551
|
+
source: 'connect-hosted',
|
|
1552
|
+
backend: spec.credentialStore,
|
|
1553
|
+
},
|
|
1554
|
+
credentialStoreOptionsFrom({ env, runtime }),
|
|
1555
|
+
);
|
|
1556
|
+
writeStoredCredentialLines(io, stored, binding);
|
|
1557
|
+
return stored;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
async function runSyncCredentialCommand(io, runtime, options) {
|
|
1561
|
+
const env = runtime.env ?? process.env;
|
|
1562
|
+
const storeOptions = credentialStoreOptionsFrom({ env, runtime });
|
|
1563
|
+
|
|
1564
|
+
if (options.action === 'list') {
|
|
1565
|
+
const listing = await listSyncCredentials(storeOptions);
|
|
1566
|
+
const cwd = runtime.cwd ? path.resolve(runtime.cwd) : process.cwd();
|
|
1567
|
+
const { bindings } = await readRootSyncBindings(cwd, options.rootDir).catch(() => ({ bindings: [] }));
|
|
1568
|
+
const boundKeys = new Map();
|
|
1569
|
+
for (const binding of bindings) {
|
|
1570
|
+
try {
|
|
1571
|
+
boundKeys.set(credentialStoreKey(binding.apiUrl, binding.projectId), binding);
|
|
1572
|
+
} catch {
|
|
1573
|
+
// an unparseable binding URL cannot be annotated; the entry still lists
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
if (options.json) {
|
|
1577
|
+
io.stdout.write(`${JSON.stringify({ storePath: listing.storePath, entries: listing.entries }, null, 2)}\n`);
|
|
1578
|
+
return 0;
|
|
1579
|
+
}
|
|
1580
|
+
io.stdout.write(`Credential store: ${listing.storePath} (${listing.entries.length} ${listing.entries.length === 1 ? 'entry' : 'entries'})\n`);
|
|
1581
|
+
if (listing.entries.length === 0) {
|
|
1582
|
+
io.stdout.write('No stored sync tokens. Run "vibecompass login" to sign in from this terminal.\n');
|
|
1583
|
+
return 0;
|
|
1584
|
+
}
|
|
1585
|
+
for (const entry of listing.entries) {
|
|
1586
|
+
const bound = boundKeys.get(entry.key);
|
|
1587
|
+
const boundNote = bound ? (bound.target ? ` — bound here as target ${bound.target}` : ' — bound here') : '';
|
|
1588
|
+
io.stdout.write(
|
|
1589
|
+
`- ${entry.apiUrl} project ${entry.projectId}: ${entry.backend}, prefix ${entry.tokenPrefix ?? '?'}, stored ${entry.storedAt ?? '?'} via ${entry.source ?? '?'}${boundNote}\n`,
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
return 0;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
const binding = await resolveSyncCredentialBinding(runtime, options);
|
|
1596
|
+
|
|
1597
|
+
if (options.action === 'remove') {
|
|
1598
|
+
const result = await removeSyncCredential(binding, storeOptions);
|
|
1599
|
+
io.stdout.write(
|
|
1600
|
+
result.removed
|
|
1601
|
+
? `Removed the stored sync token for ${binding.apiUrl} (project ${binding.projectId}) from the ${result.backend === 'keychain' ? 'OS keychain' : 'credential store file'}.\n`
|
|
1602
|
+
: `No stored sync token for ${binding.apiUrl} (project ${binding.projectId}).\n`,
|
|
1603
|
+
);
|
|
1604
|
+
io.stdout.write('Revoke the credential on the hosted dashboard under Setup -> Hosted sync if it should stop working everywhere.\n');
|
|
1605
|
+
return 0;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
let token = null;
|
|
1609
|
+
if (options.tokenStdin) {
|
|
1610
|
+
token = await readSecretFromStdin(io, runtime);
|
|
1611
|
+
if (!token) {
|
|
1612
|
+
throw new Error('--token-stdin was given but no token arrived on stdin.');
|
|
1613
|
+
}
|
|
1614
|
+
} else if (options.fromEnv) {
|
|
1615
|
+
token = typeof env[binding.credentialEnvVar] === 'string' ? env[binding.credentialEnvVar].trim() : '';
|
|
1616
|
+
if (!token) {
|
|
1617
|
+
throw new Error(`--from-env requires ${binding.credentialEnvVar} to be set in this shell.`);
|
|
1618
|
+
}
|
|
1619
|
+
} else if (canPromptSecret(io, runtime)) {
|
|
1620
|
+
token = await promptSecret('Paste the sync token from the dashboard (input hidden)', io, runtime);
|
|
1621
|
+
if (!token) {
|
|
1622
|
+
throw new Error('No token entered.');
|
|
1623
|
+
}
|
|
1624
|
+
} else {
|
|
1625
|
+
throw new Error('sync-credential set needs a token: run it in an interactive terminal, pipe one with --token-stdin, or use --from-env.');
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
const stored = await storeSyncCredential(
|
|
1629
|
+
{
|
|
1630
|
+
apiUrl: binding.apiUrl,
|
|
1631
|
+
projectId: binding.projectId,
|
|
1632
|
+
token,
|
|
1633
|
+
label: options.label ?? `VibeCompass sync token (${binding.projectId})`,
|
|
1634
|
+
source: 'sync-credential',
|
|
1635
|
+
backend: options.credentialStore,
|
|
1636
|
+
},
|
|
1637
|
+
storeOptions,
|
|
1638
|
+
);
|
|
1639
|
+
writeStoredCredentialLines(io, stored, binding);
|
|
1640
|
+
return 0;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1170
1643
|
function parseStatusArgs(argv) {
|
|
1171
1644
|
const parsed = {};
|
|
1172
1645
|
|
|
@@ -1393,6 +1866,11 @@ function parseListSessionsArgs(argv) {
|
|
|
1393
1866
|
throw new Error(`Unexpected argument "${token}".`);
|
|
1394
1867
|
}
|
|
1395
1868
|
|
|
1869
|
+
if (token === '--json') {
|
|
1870
|
+
parsed.json = true;
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1396
1874
|
const value = argv[index + 1];
|
|
1397
1875
|
if (value === undefined) {
|
|
1398
1876
|
throw new Error(`Flag "${token}" requires a value.`);
|
|
@@ -1636,6 +2114,77 @@ function parseNextDecisionIdArgs(argv) {
|
|
|
1636
2114
|
return { command: 'next-decision-id', options: parsed };
|
|
1637
2115
|
}
|
|
1638
2116
|
|
|
2117
|
+
function parseContinueSessionArgs(argv) {
|
|
2118
|
+
const parsed = {};
|
|
2119
|
+
const positional = [];
|
|
2120
|
+
|
|
2121
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2122
|
+
const token = argv[index];
|
|
2123
|
+
|
|
2124
|
+
if (!token.startsWith('--')) {
|
|
2125
|
+
positional.push(token);
|
|
2126
|
+
continue;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
if (token === '--json') {
|
|
2130
|
+
parsed.json = true;
|
|
2131
|
+
continue;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
const value = argv[index + 1];
|
|
2135
|
+
if (value === undefined) {
|
|
2136
|
+
throw new Error(`Flag "${token}" requires a value.`);
|
|
2137
|
+
}
|
|
2138
|
+
index += 1;
|
|
2139
|
+
|
|
2140
|
+
switch (token) {
|
|
2141
|
+
case '--root':
|
|
2142
|
+
parsed.rootDir = value;
|
|
2143
|
+
break;
|
|
2144
|
+
case '--tooling-root':
|
|
2145
|
+
parsed.toolingRootDir = value;
|
|
2146
|
+
break;
|
|
2147
|
+
case '--session':
|
|
2148
|
+
parsed.sessionId = value;
|
|
2149
|
+
break;
|
|
2150
|
+
case '--working-on':
|
|
2151
|
+
parsed.workingOn = value;
|
|
2152
|
+
break;
|
|
2153
|
+
case '--date':
|
|
2154
|
+
parsed.date = value;
|
|
2155
|
+
break;
|
|
2156
|
+
case '--last-thing-completed':
|
|
2157
|
+
parsed.lastThingCompleted = value;
|
|
2158
|
+
break;
|
|
2159
|
+
case '--blockers':
|
|
2160
|
+
parsed.blockers = value;
|
|
2161
|
+
break;
|
|
2162
|
+
case '--next-session-should':
|
|
2163
|
+
parsed.nextSessionShould = value;
|
|
2164
|
+
break;
|
|
2165
|
+
default:
|
|
2166
|
+
throw new Error(`Unknown flag "${token}".`);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
if (positional.length > 1) {
|
|
2171
|
+
throw new Error(`Unexpected argument "${positional[1]}".`);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
if (parsed.sessionId && positional[0] && parsed.sessionId !== positional[0]) {
|
|
2175
|
+
throw new Error(`Conflicting lane IDs: positional "${positional[0]}" and --session "${parsed.sessionId}".`);
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
if (!parsed.sessionId && positional[0]) {
|
|
2179
|
+
parsed.sessionId = positional[0];
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
return {
|
|
2183
|
+
command: 'continue-session',
|
|
2184
|
+
options: parsed,
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
|
|
1639
2188
|
function parseSwitchSessionArgs(argv) {
|
|
1640
2189
|
const parsed = {};
|
|
1641
2190
|
const positional = [];
|
|
@@ -2361,15 +2910,20 @@ function usageText() {
|
|
|
2361
2910
|
' vibecompass --version',
|
|
2362
2911
|
' vibecompass version',
|
|
2363
2912
|
' vibecompass init --name <project-name> --mode <local-only|local-primary|hosted-only> --repo <id=remote> [options]',
|
|
2364
|
-
' vibecompass
|
|
2913
|
+
' vibecompass login [--sync-target <name>] [--api-url <url>] [--no-browser] [options]',
|
|
2914
|
+
' Recommended: connect this root to hosted VibeCompass from your browser',
|
|
2365
2915
|
' vibecompass sync-target [<name>] [options]',
|
|
2916
|
+
' vibecompass connect-hosted [options] Advanced: bind with a dashboard token (login does this for you)',
|
|
2917
|
+
' vibecompass sync-credential <set|remove|list> [--sync-target <name>] [options]',
|
|
2918
|
+
' Advanced: manage stored tokens for CI or scripts',
|
|
2366
2919
|
' vibecompass status [options]',
|
|
2367
2920
|
' vibecompass refresh-workflow [--dry-run|--apply] [options]',
|
|
2368
2921
|
' vibecompass docs-update [--session <lane-id>] [options]',
|
|
2369
2922
|
' vibecompass start-session --id <lane-id> --working-on <text> [--branch <name> [--worktree]] [options]',
|
|
2370
2923
|
' vibecompass close-session --title <text> --completed <text> --architecture-docs <status> --decision-log <status> --session-maintenance <status> [options]',
|
|
2371
2924
|
' vibecompass end-session --title <text> --completed <text> --architecture-docs <status> --decision-log <status> --session-maintenance <status> [options] # alias',
|
|
2372
|
-
' vibecompass
|
|
2925
|
+
' vibecompass continue-session [<lane-id>] [--session <lane-id>] [--working-on <text>] [--json] [options]',
|
|
2926
|
+
' vibecompass list-sessions [--json] [options]',
|
|
2373
2927
|
' vibecompass switch-session <id> [options]',
|
|
2374
2928
|
' vibecompass rebuild-active-index [--current <lane-id>] [options]',
|
|
2375
2929
|
' vibecompass write-lane-marker --session <lane-id> [--dir <path>] [options]',
|
|
@@ -2414,13 +2968,17 @@ function usageText() {
|
|
|
2414
2968
|
' --force Overwrite an existing project.yaml',
|
|
2415
2969
|
' --replace-active-lanes Allow --force to replace a root that has active session lanes',
|
|
2416
2970
|
'',
|
|
2417
|
-
'Connect-hosted options:',
|
|
2971
|
+
'Connect-hosted options (advanced; on a terminal, Enter at the token prompt starts the browser sign-in):',
|
|
2418
2972
|
' --root <path> Project-memory root. Defaults to .compass',
|
|
2419
2973
|
' --target <name> Add or update a named sync target (e.g. dev, prod)',
|
|
2420
2974
|
' First named target becomes the default; flat sync fields mirror the default target',
|
|
2421
2975
|
' --sync-api-url <url> Hosted sync api_url',
|
|
2422
2976
|
' --sync-project-id <id> Hosted sync project_id',
|
|
2423
2977
|
' --sync-credential-env-var <name> Hosted sync env var reference',
|
|
2978
|
+
' --token-stdin Store the sync token read from stdin (first non-empty line)',
|
|
2979
|
+
' --no-store Do not store a token; rely on the env var only',
|
|
2980
|
+
' --credential-store <auto|keychain|file>',
|
|
2981
|
+
' Where to keep the token (D-355). auto prefers the OS keychain',
|
|
2424
2982
|
' Without --target, replaces any existing flat project.yaml sync binding',
|
|
2425
2983
|
'',
|
|
2426
2984
|
'Sync-target options:',
|
|
@@ -2428,6 +2986,33 @@ function usageText() {
|
|
|
2428
2986
|
' vibecompass sync-target <name> Switch the default sync target (re-mirrors flat sync fields)',
|
|
2429
2987
|
' --root <path> Project-memory root. Defaults to .compass',
|
|
2430
2988
|
'',
|
|
2989
|
+
'Login options (recommended path, D-355/D-356):',
|
|
2990
|
+
' vibecompass login Sign in from the terminal: opens the dashboard, you approve a short code, the sync token is stored locally',
|
|
2991
|
+
' --root <path> Project-memory root. Defaults to .compass',
|
|
2992
|
+
' --sync-target <name> Sign in for a named sync target (add --api-url to create a new one)',
|
|
2993
|
+
' --api-url <url> Hosted URL for an unbound root or a new target. Defaults to https://vibecompass.dev',
|
|
2994
|
+
' --project-id <id> Pre-select a hosted project on the approval page',
|
|
2995
|
+
' --sync-credential-env-var <name> Override env-var name recorded in a new binding. Defaults to VIBECOMPASS_SYNC_TOKEN',
|
|
2996
|
+
' --label <text> Device label shown in the dashboard. Defaults to the hostname',
|
|
2997
|
+
' --credential-store <auto|keychain|file>',
|
|
2998
|
+
' Where to keep the token. auto prefers the OS keychain',
|
|
2999
|
+
' --poll-timeout <seconds> Stop waiting for approval after this long. Defaults to the code lifetime',
|
|
3000
|
+
' --no-browser Print the link without opening a browser',
|
|
3001
|
+
'',
|
|
3002
|
+
'Sync-credential options (advanced, D-355):',
|
|
3003
|
+
' vibecompass sync-credential set Store a dashboard token for the bound project (hidden prompt, --token-stdin, or --from-env)',
|
|
3004
|
+
' vibecompass sync-credential remove Forget the stored token for the bound project',
|
|
3005
|
+
' vibecompass sync-credential list List stored tokens (metadata only, never values)',
|
|
3006
|
+
' --root <path> Project-memory root. Defaults to .compass',
|
|
3007
|
+
' --sync-target <name> Address a named sync target',
|
|
3008
|
+
' --api-url <url> --project-id <id> Address a hosted project without a bound root',
|
|
3009
|
+
' --token-stdin Read the token from stdin (first non-empty line)',
|
|
3010
|
+
' --from-env Store the value currently in the binding env var',
|
|
3011
|
+
' --credential-store <auto|keychain|file>',
|
|
3012
|
+
' Where to keep the token. auto prefers the OS keychain',
|
|
3013
|
+
' --label <text> Label recorded with the stored token',
|
|
3014
|
+
' --json list: print entries as JSON',
|
|
3015
|
+
'',
|
|
2431
3016
|
'Status options:',
|
|
2432
3017
|
' --root <path> Project-memory root. Defaults to .compass',
|
|
2433
3018
|
' --tooling-root <path> Directory where agent files are inspected. Defaults to cwd',
|
|
@@ -2481,9 +3066,21 @@ function usageText() {
|
|
|
2481
3066
|
' --last-thing-completed <text> Optional override for the CLAUDE.md completed summary',
|
|
2482
3067
|
' --next-session-should <text> Optional override for the CLAUDE.md next-session summary',
|
|
2483
3068
|
'',
|
|
3069
|
+
'Continue-session options (D-353):',
|
|
3070
|
+
' --root <path> Project-memory root. Explicit --root wins; otherwise the nearest worktree lane marker supplies it, else .compass',
|
|
3071
|
+
' --tooling-root <path> Tooling root that contains CLAUDE.md. Defaults to cwd; follows the memory root placement when a marker supplies the root',
|
|
3072
|
+
' --session <lane-id> Lane to resume; positional ID is also accepted. Omitted: nearest worktree lane marker, else the single active lane; 2+ active lanes fail closed and print the lane inventory (D-277)',
|
|
3073
|
+
' --working-on <text> Optional refreshed lane scope; rewrites working_on in session.yaml, the index, wip.md, and CLAUDE.md',
|
|
3074
|
+
' --date <YYYY-MM-DD> Optional explicit resume date (the lane keeps its opening date and session number)',
|
|
3075
|
+
' --last-thing-completed <text> Optional override for the CLAUDE.md current-session block',
|
|
3076
|
+
' --blockers <text> Optional current blockers summary',
|
|
3077
|
+
' --next-session-should <text> Optional current-session handoff summary',
|
|
3078
|
+
' --json Print the resume result (lane, paths, runtime, other lanes, warnings) as JSON',
|
|
3079
|
+
'',
|
|
2484
3080
|
'List/switch-session options:',
|
|
2485
3081
|
' --root <path> Project-memory root. Defaults to .compass',
|
|
2486
3082
|
' --session <lane-id> Lane ID for switch-session; positional ID is also accepted',
|
|
3083
|
+
' --json list-sessions: print the lane inventory as JSON',
|
|
2487
3084
|
'',
|
|
2488
3085
|
'Rebuild-active-index options:',
|
|
2489
3086
|
' --root <path> Project-memory root. Defaults to .compass',
|