@synkro-sh/cli 1.10.5 → 1.10.6
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/dist/bootstrap.js +522 -131
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -147,7 +147,7 @@ function getIdentity() {
|
|
|
147
147
|
if (cached2) return cached2;
|
|
148
148
|
let cliVersion2 = "0.0.0";
|
|
149
149
|
try {
|
|
150
|
-
cliVersion2 = "1.10.
|
|
150
|
+
cliVersion2 = "1.10.6";
|
|
151
151
|
} catch {
|
|
152
152
|
}
|
|
153
153
|
const creds = loadCredentialsIdentity();
|
|
@@ -3255,10 +3255,15 @@ function pruneToolCallMarks(now: number): void {
|
|
|
3255
3255
|
|
|
3256
3256
|
// The user's answer to "move into the task worktree, or stay here?". Written by the
|
|
3257
3257
|
// "synkro workspace stay" command (the CLI owns ~/.synkro, so nothing hand-writes it)
|
|
3258
|
-
// and read here.
|
|
3259
|
-
//
|
|
3258
|
+
// and read here. The filename includes a one-way session key so consent in one
|
|
3259
|
+
// Claude/Codex chat can never silently authorize another chat working the task.
|
|
3260
3260
|
const WORKSPACE_CHOICE_DIR = join(HOME, '.synkro', 'workspace-choice');
|
|
3261
3261
|
|
|
3262
|
+
function taskWorkspaceChoicePath(taskId: string, sessionId: string): string {
|
|
3263
|
+
const sessionKey = createHash('sha256').update(String(sessionId || '')).digest('hex').slice(0, 16);
|
|
3264
|
+
return join(WORKSPACE_CHOICE_DIR, taskId + '.' + sessionKey + '.stay');
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3262
3267
|
// The user answers the workspace question in plain language on their next
|
|
3263
3268
|
// prompt. Writing the marker is exactly what the CLI's "workspace stay"
|
|
3264
3269
|
// command does — but the consent matcher only accepts one literal spelling,
|
|
@@ -3279,14 +3284,23 @@ function isWorkspaceStayIntent(prompt: string): boolean {
|
|
|
3279
3284
|
].some((pattern) => pattern.test(normalized));
|
|
3280
3285
|
}
|
|
3281
3286
|
|
|
3282
|
-
function taskWorkspaceStayRecorded(taskId: string): boolean {
|
|
3283
|
-
if (!/^task_[a-z0-9]{8}$/i.test(String(taskId || ''))) return false;
|
|
3284
|
-
try { return existsSync(
|
|
3287
|
+
function taskWorkspaceStayRecorded(taskId: string, sessionId: string): boolean {
|
|
3288
|
+
if (!/^task_[a-z0-9]{8}$/i.test(String(taskId || '')) || !sessionId) return false;
|
|
3289
|
+
try { return existsSync(taskWorkspaceChoicePath(taskId, sessionId)); } catch { return false; }
|
|
3285
3290
|
}
|
|
3286
3291
|
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3292
|
+
function recordTaskWorkspaceStay(taskId: string, sessionId: string): boolean {
|
|
3293
|
+
if (!/^task_[a-z0-9]{8}$/i.test(String(taskId || '')) || !sessionId) return false;
|
|
3294
|
+
try {
|
|
3295
|
+
mkdirSync(WORKSPACE_CHOICE_DIR, { recursive: true });
|
|
3296
|
+
writeFileSync(taskWorkspaceChoicePath(taskId, sessionId), new Date().toISOString() + '\n', { mode: 0o600 });
|
|
3297
|
+
return true;
|
|
3298
|
+
} catch { return false; }
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
// Our own context string, so the shape is stable: '... task=<id> branch=<name> ...'.
|
|
3302
|
+
function fieldFromScmContext(context: string, field: string): string {
|
|
3303
|
+
const marker = ' ' + field + '=';
|
|
3290
3304
|
const at = String(context || '').indexOf(marker);
|
|
3291
3305
|
if (at === -1) return '';
|
|
3292
3306
|
const rest = context.slice(at + marker.length);
|
|
@@ -3294,14 +3308,73 @@ function taskIdFromScmContext(context: string): string {
|
|
|
3294
3308
|
return (end === -1 ? rest : rest.slice(0, end)).trim();
|
|
3295
3309
|
}
|
|
3296
3310
|
|
|
3311
|
+
function taskIdFromScmContext(context: string): string {
|
|
3312
|
+
return fieldFromScmContext(context, 'task');
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
// An exact-command allowance must not be defeated by the output plumbing agents
|
|
3316
|
+
// reflexively append ('2>&1 | tail -2') — requiring the bare spelling made the
|
|
3317
|
+
// gate's own escape hatch block itself (observed live). Only harmless trailing
|
|
3318
|
+
// redirection plus a single display filter pass; anything that could chain a
|
|
3319
|
+
// second command (&&, ;, extra pipes, substitution) still rejects.
|
|
3320
|
+
function isBareCommandWithPlumbing(command: string, head: string): boolean {
|
|
3321
|
+
if (!command.startsWith(head)) return false;
|
|
3322
|
+
const rest = command.slice(head.length);
|
|
3323
|
+
return /^(?:\s+2>&1)?(?:\s*\|\s*(?:tail|head|cat)(?:\s+[-\w.+]+)*)?\s*$/.test(rest);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
function bashCommandOf(payload: any): string {
|
|
3327
|
+
if (String(payload?.tool_name || '') !== 'Bash') return '';
|
|
3328
|
+
const input = payload?.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
|
|
3329
|
+
return String(input.command || input.cmd || '').trim();
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3297
3332
|
// Consent must never deadlock behind the block it resolves: the command that records
|
|
3298
3333
|
// the answer is allowed through for the task currently being asked about, and nothing
|
|
3299
3334
|
// else is.
|
|
3300
|
-
function
|
|
3301
|
-
|
|
3335
|
+
function workspaceReadOnlyShellSegment(segment: string): boolean {
|
|
3336
|
+
const value = String(segment || '').trim();
|
|
3337
|
+
if (!value || /[;&><$]/.test(value) || value.includes(String.fromCharCode(96))) return false;
|
|
3338
|
+
const tokens = value.split(/\s+/);
|
|
3339
|
+
const verb = String(tokens[0] || '').toLowerCase();
|
|
3340
|
+
const writeOptions = new Set(['-i', '--in-place', '-o', '--output', '--output-document']);
|
|
3341
|
+
if (tokens.some((token) => writeOptions.has(token) || token.startsWith('--output='))) return false;
|
|
3342
|
+
const reads = new Set(['cat', 'head', 'tail', 'grep', 'egrep', 'fgrep', 'rg', 'ag', 'find', 'fd', 'ls', 'wc', 'cmp', 'diff', 'file', 'stat', 'pwd', 'sort', 'uniq', 'cut', 'tr', 'jq', 'yq']);
|
|
3343
|
+
if (reads.has(verb)) {
|
|
3344
|
+
if ((verb === 'find' || verb === 'fd') && tokens.some((token) => new Set(['-exec', '-execdir', '-ok', '-okdir', '-delete', '-fprint', '-fprint0', '-fls', '--exec', '--exec-batch']).has(token))) return false;
|
|
3345
|
+
return true;
|
|
3346
|
+
}
|
|
3347
|
+
if (verb !== 'git') return false;
|
|
3348
|
+
return new Set(['log', 'show', 'diff', 'blame', 'status', 'rev-parse', 'ls-files', 'ls-tree', 'cat-file', 'shortlog', 'describe']).has(String(tokens[1] || '').toLowerCase());
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
function isWorkspaceReadOnlyDiagnostic(payload: any): boolean {
|
|
3352
|
+
const toolName = String(payload?.tool_name || '');
|
|
3353
|
+
if (/^(?:Read|ReadFile|read_file|Grep|grep_search|codebase_search|file_search|Glob|list_dir)$/i.test(toolName)) return true;
|
|
3354
|
+
if (!/^(?:Bash|Shell|terminal|run_terminal_cmd|execute_command)$/i.test(toolName)) return false;
|
|
3302
3355
|
const input = payload?.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
|
|
3303
3356
|
const command = String(input.command || input.cmd || '').trim();
|
|
3304
|
-
|
|
3357
|
+
if (!command || /\|\||(^|[^&])&([^&]|$)|;/.test(command)) return false;
|
|
3358
|
+
return command.split('|').every(workspaceReadOnlyShellSegment);
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
function isWorkspaceConsentCommand(payload: any, taskId: string): boolean {
|
|
3362
|
+
if (!taskId) return false;
|
|
3363
|
+
const command = bashCommandOf(payload);
|
|
3364
|
+
return Boolean(command) && isBareCommandWithPlumbing(command, 'synkro workspace stay ' + taskId);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
// A session that is already inside the task worktree but on the wrong branch needs
|
|
3368
|
+
// exactly one repair: checking out the canonical branch. The gate demands that
|
|
3369
|
+
// branch, so blocking the checkout that reaches it would deadlock (observed live
|
|
3370
|
+
// after an agent created a side branch in the task worktree). Elsewhere the command
|
|
3371
|
+
// is harmless: git itself refuses to check out a branch held by another worktree.
|
|
3372
|
+
function isWorkspaceRepairCommand(payload: any, branchName: string): boolean {
|
|
3373
|
+
if (!branchName) return false;
|
|
3374
|
+
const command = bashCommandOf(payload);
|
|
3375
|
+
if (!command) return false;
|
|
3376
|
+
return isBareCommandWithPlumbing(command, 'git checkout ' + branchName)
|
|
3377
|
+
|| isBareCommandWithPlumbing(command, 'git switch ' + branchName);
|
|
3305
3378
|
}
|
|
3306
3379
|
|
|
3307
3380
|
function firstHookForToolCall(sessionId: string, payload: any): boolean {
|
|
@@ -3666,7 +3739,7 @@ function shellTaskWorkspaceArg(value: string): string {
|
|
|
3666
3739
|
return "'" + value.replace(/'/g, "'\"'\"'") + "'";
|
|
3667
3740
|
}
|
|
3668
3741
|
|
|
3669
|
-
function taskScmWorkspaceInstruction(harness: string, sessionId: string, workspace: any): string {
|
|
3742
|
+
function taskScmWorkspaceInstruction(harness: string, sessionId: string, workspace: any, root = ''): string {
|
|
3670
3743
|
const context = taskScmWorkspaceContext(workspace, harness);
|
|
3671
3744
|
const worktreePath = String(workspace?.worktreePath || '');
|
|
3672
3745
|
const branchName = String(workspace?.branchName || '');
|
|
@@ -3676,6 +3749,15 @@ function taskScmWorkspaceInstruction(harness: string, sessionId: string, workspa
|
|
|
3676
3749
|
+ 'the task worktree and canonical branch.';
|
|
3677
3750
|
if (harness === 'cc') {
|
|
3678
3751
|
if (!worktreePath) return context + '\nTask worktree creation failed closed; retry workspace preparation.';
|
|
3752
|
+
// Already inside the task worktree, just not on its branch: the move/stay
|
|
3753
|
+
// question was answered the moment the session got here, and re-asking it
|
|
3754
|
+
// offers two exits that both fail. Name the one command that repairs it —
|
|
3755
|
+
// the gate lets exactly that command through.
|
|
3756
|
+
if (root && samePath(root, worktreePath)) {
|
|
3757
|
+
return context + '\nThis session is already in the task worktree but not on its canonical branch. '
|
|
3758
|
+
+ 'Run "git checkout ' + branchName + '" to continue; that command is allowed through this gate, '
|
|
3759
|
+
+ 'and substantive tools stay blocked until the branch matches.';
|
|
3760
|
+
}
|
|
3679
3761
|
// Ask, do not command. The user may legitimately want to keep working where they
|
|
3680
3762
|
// are, and Synkro should not move them without their say-so.
|
|
3681
3763
|
return context + '\nAsk the user whether to move this task into its isolated worktree '
|
|
@@ -3691,14 +3773,18 @@ function taskScmWorkspaceInstruction(harness: string, sessionId: string, workspa
|
|
|
3691
3773
|
+ '. Confirm the resumed workspace is on branch ' + JSON.stringify(branchName) + '. ' + requirement;
|
|
3692
3774
|
}
|
|
3693
3775
|
if (!worktreePath) {
|
|
3694
|
-
return context + '\
|
|
3695
|
-
+
|
|
3696
|
-
+ '
|
|
3697
|
-
+ '
|
|
3776
|
+
return context + '\nAsk the user whether to move this task into a generated isolated Codex worktree on branch '
|
|
3777
|
+
+ JSON.stringify(branchName) + ', or keep working in the current workspace. Do not decide for them. '
|
|
3778
|
+
+ 'To move: use Codex\'s Environment control or native Handoff for this task; never hand off a different task as a workaround. '
|
|
3779
|
+
+ 'To stay: run "synkro workspace stay ' + String(workspace?.taskId || '') + '". '
|
|
3780
|
+
+ 'Never switch the shared Local checkout.';
|
|
3698
3781
|
}
|
|
3699
|
-
return context + '\
|
|
3700
|
-
+
|
|
3701
|
-
+ '
|
|
3782
|
+
return context + '\nAsk the user whether to move this task to its prepared worktree '
|
|
3783
|
+
+ JSON.stringify(worktreePath) + ' on branch ' + JSON.stringify(branchName)
|
|
3784
|
+
+ ', or keep working in the current isolated Codex worktree. Do not decide for them. '
|
|
3785
|
+
+ 'To move: use Codex\'s Environment control or native Handoff for this task; never hand off a different task as a workaround. '
|
|
3786
|
+
+ 'To stay: run "synkro workspace stay ' + String(workspace?.taskId || '') + '". '
|
|
3787
|
+
+ 'Never switch the shared Local checkout.';
|
|
3702
3788
|
}
|
|
3703
3789
|
|
|
3704
3790
|
// CC validates hookSpecificOutput.hookEventName against the event that actually fired.
|
|
@@ -3780,7 +3866,7 @@ async function reconcileTaskScm(
|
|
|
3780
3866
|
: result?.workspace;
|
|
3781
3867
|
return {
|
|
3782
3868
|
reason: result?.waiting
|
|
3783
|
-
? (taskScmWorkspaceInstruction(harness, sessionId, workspace)
|
|
3869
|
+
? (taskScmWorkspaceInstruction(harness, sessionId, workspace, root)
|
|
3784
3870
|
|| String(result.reason || 'task source-control preparation is pending'))
|
|
3785
3871
|
: '',
|
|
3786
3872
|
context: taskScmWorkspaceContext(workspace, harness),
|
|
@@ -4479,19 +4565,21 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
4479
4565
|
// prompt records the same durable marker the CLI command writes. Reconcile
|
|
4480
4566
|
// just told us WHICH task is being asked, so no extra state is needed and
|
|
4481
4567
|
// ordinary prompts outside a pending ask are never scanned.
|
|
4482
|
-
if (surface === 'prompt-submit' && scm.reason && scmTaskId && !taskWorkspaceStayRecorded(scmTaskId)) {
|
|
4568
|
+
if (surface === 'prompt-submit' && scm.reason && scmTaskId && !taskWorkspaceStayRecorded(scmTaskId, sessionId)) {
|
|
4483
4569
|
const promptText = String(payload.prompt || payload.user_message || '');
|
|
4484
4570
|
if (isWorkspaceStayIntent(promptText)) {
|
|
4485
|
-
|
|
4486
|
-
mkdirSync(WORKSPACE_CHOICE_DIR, { recursive: true });
|
|
4487
|
-
writeFileSync(join(WORKSPACE_CHOICE_DIR, scmTaskId + '.stay'), new Date().toISOString() + '\n');
|
|
4488
|
-
} catch { /* fail-open: the CLI command and the exact-string path remain */ }
|
|
4571
|
+
recordTaskWorkspaceStay(scmTaskId, sessionId);
|
|
4489
4572
|
}
|
|
4490
4573
|
}
|
|
4491
|
-
|
|
4574
|
+
const workspaceConsentCommand = Boolean(scmTaskId) && isWorkspaceConsentCommand(payload, scmTaskId);
|
|
4575
|
+
if (workspaceConsentCommand) recordTaskWorkspaceStay(scmTaskId, sessionId);
|
|
4576
|
+
// The user was asked and chose to keep working here, is answering right now,
|
|
4577
|
+
// or the tool call is the one git command that repairs the workspace itself.
|
|
4492
4578
|
const workspaceConsentSettled = Boolean(scmTaskId)
|
|
4493
|
-
&& (taskWorkspaceStayRecorded(scmTaskId
|
|
4494
|
-
|
|
4579
|
+
&& (taskWorkspaceStayRecorded(scmTaskId, sessionId)
|
|
4580
|
+
|| workspaceConsentCommand
|
|
4581
|
+
|| isWorkspaceRepairCommand(payload, fieldFromScmContext(scm.context, 'branch')));
|
|
4582
|
+
if (scm.reason && substantiveTool && !workspaceConsentSettled && !isWorkspaceReadOnlyDiagnostic(payload)) {
|
|
4495
4583
|
out(taskScmBlockResponse(harness, scm.reason, firstHookForToolCall(sessionId, payload)));
|
|
4496
4584
|
return;
|
|
4497
4585
|
}
|
|
@@ -4680,7 +4768,7 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
4680
4768
|
let scmContext = scm.context && surface === 'prompt-submit' && firstHookForToolCall(sessionId, payload) ? scm.context : '';
|
|
4681
4769
|
// Say plainly that work is continuing outside the task worktree by the user's choice,
|
|
4682
4770
|
// so the state is never mistaken for a binding that silently failed.
|
|
4683
|
-
if (scmContext && scmTaskId && taskWorkspaceStayRecorded(scmTaskId)) {
|
|
4771
|
+
if (scmContext && scmTaskId && taskWorkspaceStayRecorded(scmTaskId, sessionId)) {
|
|
4684
4772
|
scmContext += ' workspace=staying-here-by-user-choice';
|
|
4685
4773
|
}
|
|
4686
4774
|
const responseText = withActualHookEvent(withTaskScmContext(contractResponseText, harness, scmContext), payload);
|
|
@@ -5023,6 +5111,7 @@ if (process.env.SYNKRO_HOOK_FORMAT === 'codex' && hookEventName === 'PermissionR
|
|
|
5023
5111
|
// cli/auth/stub.ts
|
|
5024
5112
|
var stub_exports = {};
|
|
5025
5113
|
__export(stub_exports, {
|
|
5114
|
+
accessTokenExpired: () => accessTokenExpired,
|
|
5026
5115
|
authedFetch: () => authedFetch,
|
|
5027
5116
|
authenticate: () => authenticate,
|
|
5028
5117
|
clearCredentials: () => clearCredentials,
|
|
@@ -5280,19 +5369,23 @@ function getAccessToken() {
|
|
|
5280
5369
|
const creds = loadCredentials();
|
|
5281
5370
|
return creds?.access_token || null;
|
|
5282
5371
|
}
|
|
5283
|
-
function
|
|
5284
|
-
const creds = loadCredentials();
|
|
5285
|
-
if (!creds) return true;
|
|
5372
|
+
function accessTokenExpired(accessToken, now = Date.now()) {
|
|
5286
5373
|
try {
|
|
5287
|
-
const decoded = jwt.decode(
|
|
5374
|
+
const decoded = jwt.decode(accessToken);
|
|
5288
5375
|
if (!decoded?.exp) return true;
|
|
5289
5376
|
const expiresAt = decoded.exp * 1e3;
|
|
5290
|
-
const
|
|
5291
|
-
|
|
5377
|
+
const lifetimeMs = decoded.iat ? (decoded.exp - decoded.iat) * 1e3 : 0;
|
|
5378
|
+
const buffer = lifetimeMs > 0 ? Math.min(MAX_EXPIRY_BUFFER_MS, Math.floor(lifetimeMs / 5)) : MAX_EXPIRY_BUFFER_MS;
|
|
5379
|
+
return now > expiresAt - buffer;
|
|
5292
5380
|
} catch {
|
|
5293
5381
|
return true;
|
|
5294
5382
|
}
|
|
5295
5383
|
}
|
|
5384
|
+
function isTokenExpired() {
|
|
5385
|
+
const creds = loadCredentials();
|
|
5386
|
+
if (!creds?.access_token) return true;
|
|
5387
|
+
return accessTokenExpired(creds.access_token);
|
|
5388
|
+
}
|
|
5296
5389
|
async function refreshToken() {
|
|
5297
5390
|
const creds = loadCredentials();
|
|
5298
5391
|
if (!creds?.refresh_token) return false;
|
|
@@ -5360,7 +5453,7 @@ async function getSecrets(userId, integrationId) {
|
|
|
5360
5453
|
LANGSMITH_API_KEY: process.env.USER_LANGSMITH_KEY || ""
|
|
5361
5454
|
};
|
|
5362
5455
|
}
|
|
5363
|
-
var PORT, RAW_WEB_AUTH_URL, SYNKRO_WEB_AUTH_URL, AUTH_FILE, RAW_API_URL, SYNKRO_API_URL, ERROR_HTML, refreshPromise;
|
|
5456
|
+
var PORT, RAW_WEB_AUTH_URL, SYNKRO_WEB_AUTH_URL, AUTH_FILE, RAW_API_URL, SYNKRO_API_URL, ERROR_HTML, MAX_EXPIRY_BUFFER_MS, refreshPromise;
|
|
5364
5457
|
var init_stub = __esm({
|
|
5365
5458
|
"cli/auth/stub.ts"() {
|
|
5366
5459
|
"use strict";
|
|
@@ -5410,6 +5503,7 @@ var init_stub = __esm({
|
|
|
5410
5503
|
</body>
|
|
5411
5504
|
</html>
|
|
5412
5505
|
`;
|
|
5506
|
+
MAX_EXPIRY_BUFFER_MS = 60 * 1e3;
|
|
5413
5507
|
refreshPromise = null;
|
|
5414
5508
|
}
|
|
5415
5509
|
});
|
|
@@ -7840,7 +7934,7 @@ var init_dockerInstall = __esm({
|
|
|
7840
7934
|
HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
|
|
7841
7935
|
CONTAINER_NAME = resolveContainerName();
|
|
7842
7936
|
defaultImageVersion = () => {
|
|
7843
|
-
if (true) return "1.10.
|
|
7937
|
+
if (true) return "1.10.6";
|
|
7844
7938
|
try {
|
|
7845
7939
|
const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
|
|
7846
7940
|
if (pkg.version) return pkg.version;
|
|
@@ -8900,7 +8994,7 @@ function writeConfigEnv(opts) {
|
|
|
8900
8994
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
|
|
8901
8995
|
`SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
|
|
8902
8996
|
`SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
|
|
8903
|
-
`SYNKRO_VERSION=${shellQuoteSingle2("1.10.
|
|
8997
|
+
`SYNKRO_VERSION=${shellQuoteSingle2("1.10.6")}`
|
|
8904
8998
|
];
|
|
8905
8999
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
|
|
8906
9000
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
|
|
@@ -9647,7 +9741,7 @@ async function installCommand(opts = {}) {
|
|
|
9647
9741
|
await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
|
|
9648
9742
|
emit("install", {
|
|
9649
9743
|
phase: "started",
|
|
9650
|
-
cli_version_to: "1.10.
|
|
9744
|
+
cli_version_to: "1.10.6",
|
|
9651
9745
|
agents_detected: agents.map((a) => a.kind),
|
|
9652
9746
|
with_github: false,
|
|
9653
9747
|
with_local_cc: false,
|
|
@@ -14429,6 +14523,16 @@ import { homedir as homedir31 } from "os";
|
|
|
14429
14523
|
function markerPath(taskId) {
|
|
14430
14524
|
return join31(WORKSPACE_CHOICE_DIR, `${taskId}.stay`);
|
|
14431
14525
|
}
|
|
14526
|
+
function markerNamesForTask(taskId) {
|
|
14527
|
+
try {
|
|
14528
|
+
return existsSync33(WORKSPACE_CHOICE_DIR) ? readdirSync8(WORKSPACE_CHOICE_DIR).filter((name) => name === `${taskId}.stay` || name.startsWith(`${taskId}.`) && name.endsWith(".stay")) : [];
|
|
14529
|
+
} catch {
|
|
14530
|
+
return [];
|
|
14531
|
+
}
|
|
14532
|
+
}
|
|
14533
|
+
function taskIdFromMarker(name) {
|
|
14534
|
+
return (name.match(/^(task_[a-z0-9]{8})(?:\.[a-f0-9]{16})?\.stay$/i) || [])[1] || "";
|
|
14535
|
+
}
|
|
14432
14536
|
function usage() {
|
|
14433
14537
|
console.log(`synkro workspace \u2014 record where a task's work happens
|
|
14434
14538
|
|
|
@@ -14437,8 +14541,8 @@ Usage:
|
|
|
14437
14541
|
synkro workspace clear <taskId> forget the choice (the gate asks again)
|
|
14438
14542
|
synkro workspace status [taskId] show recorded choices
|
|
14439
14543
|
|
|
14440
|
-
The gate asks once per task. "stay" is remembered
|
|
14441
|
-
the active task changes.`);
|
|
14544
|
+
The gate asks once per task session. "stay" is remembered in that chat until the
|
|
14545
|
+
choice is cleared or the active task changes.`);
|
|
14442
14546
|
}
|
|
14443
14547
|
async function workspaceCommand(args2) {
|
|
14444
14548
|
const sub = String(args2[0] || "").trim();
|
|
@@ -14455,7 +14559,7 @@ async function workspaceCommand(args2) {
|
|
|
14455
14559
|
recorded = [];
|
|
14456
14560
|
}
|
|
14457
14561
|
if (taskId) {
|
|
14458
|
-
const on =
|
|
14562
|
+
const on = markerNamesForTask(taskId).length > 0;
|
|
14459
14563
|
console.log(`${taskId}: ${on ? "stay recorded" : "no choice recorded"}`);
|
|
14460
14564
|
return;
|
|
14461
14565
|
}
|
|
@@ -14464,7 +14568,7 @@ async function workspaceCommand(args2) {
|
|
|
14464
14568
|
return;
|
|
14465
14569
|
}
|
|
14466
14570
|
console.log("Staying in the current checkout for:");
|
|
14467
|
-
for (const
|
|
14571
|
+
for (const id of [...new Set(recorded.map(taskIdFromMarker).filter(Boolean))].sort()) console.log(` ${id}`);
|
|
14468
14572
|
return;
|
|
14469
14573
|
}
|
|
14470
14574
|
if (sub !== "stay" && sub !== "clear") {
|
|
@@ -14479,9 +14583,11 @@ async function workspaceCommand(args2) {
|
|
|
14479
14583
|
return;
|
|
14480
14584
|
}
|
|
14481
14585
|
if (sub === "clear") {
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14586
|
+
for (const name of markerNamesForTask(taskId)) {
|
|
14587
|
+
try {
|
|
14588
|
+
rmSync5(join31(WORKSPACE_CHOICE_DIR, name), { force: true });
|
|
14589
|
+
} catch {
|
|
14590
|
+
}
|
|
14485
14591
|
}
|
|
14486
14592
|
console.log(`Cleared the workspace choice for ${taskId}.`);
|
|
14487
14593
|
return;
|
|
@@ -15039,6 +15145,10 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
|
|
|
15039
15145
|
// focus it, click rows/chips. Without this the fixed split reads as
|
|
15040
15146
|
// "blocked in".
|
|
15041
15147
|
["set-option", "-t", UI_SESSION, "mouse", "on"],
|
|
15148
|
+
// Scrollback deep enough to hold a real working session. tmux defaults to
|
|
15149
|
+
// 2000 lines, which a long harness session blows through — the start of the
|
|
15150
|
+
// conversation would silently fall off the top with no way back to it.
|
|
15151
|
+
["set-option", "-t", UI_SESSION, "history-limit", "50000"],
|
|
15042
15152
|
["set-option", "-t", UI_SESSION, "status-position", "top"],
|
|
15043
15153
|
["set-option", "-t", UI_SESSION, "status-style", "bg=colour233,fg=colour245"],
|
|
15044
15154
|
["set-option", "-t", UI_SESSION, "status-format[0]", chipStrip(sidebarWidth)],
|
|
@@ -15064,6 +15174,7 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
|
|
|
15064
15174
|
for (const argv of style) await run(HOST, ["tmux", ...argv]);
|
|
15065
15175
|
}
|
|
15066
15176
|
async function buildTab(bootPath, repoCwd, spec) {
|
|
15177
|
+
await run(HOST, ["tmux", "set-option", "-g", "history-limit", "50000"]);
|
|
15067
15178
|
let windowTarget;
|
|
15068
15179
|
if (!await uiSessionExists()) {
|
|
15069
15180
|
const cols = String(Number(process.stdout.columns || 0) || 220);
|
|
@@ -15132,11 +15243,29 @@ async function uiSessionExists() {
|
|
|
15132
15243
|
const result = await run(HOST, ["tmux", "has-session", "-t", UI_SESSION]);
|
|
15133
15244
|
return result.ok;
|
|
15134
15245
|
}
|
|
15246
|
+
function parseCollapsedClients(listOutput) {
|
|
15247
|
+
return String(listOutput || "").split("\n").map((line) => line.trim().split("|")).filter(([name, width, height]) => Boolean(name) && Number.isFinite(Number(width)) && Number.isFinite(Number(height)) && (Number(width) < 20 || Number(height) < 5)).map(([name]) => name);
|
|
15248
|
+
}
|
|
15249
|
+
async function pruneCollapsedClients() {
|
|
15250
|
+
const listed = await run(HOST, [
|
|
15251
|
+
"tmux",
|
|
15252
|
+
"list-clients",
|
|
15253
|
+
"-t",
|
|
15254
|
+
UI_SESSION,
|
|
15255
|
+
"-F",
|
|
15256
|
+
"#{client_name}|#{client_width}|#{client_height}"
|
|
15257
|
+
]);
|
|
15258
|
+
if (!listed.ok) return 0;
|
|
15259
|
+
const collapsed = parseCollapsedClients(listed.stdout);
|
|
15260
|
+
for (const name of collapsed) await run(HOST, ["tmux", "detach-client", "-t", name]);
|
|
15261
|
+
return collapsed.length;
|
|
15262
|
+
}
|
|
15135
15263
|
async function launchUi(bootPath, repoCwd) {
|
|
15136
15264
|
rememberSpace(repoCwd);
|
|
15137
15265
|
if (!await uiSessionExists()) {
|
|
15138
15266
|
await buildTab(bootPath, repoCwd, { cwd: repoCwd, center: makeTerminalCommand(bootPath), focus: "sidebar" });
|
|
15139
15267
|
}
|
|
15268
|
+
await pruneCollapsedClients();
|
|
15140
15269
|
return runInherit(process.env.TMUX ? ["tmux", "switch-client", "-t", UI_SESSION] : ["tmux", "attach-session", "-t", UI_SESSION]);
|
|
15141
15270
|
}
|
|
15142
15271
|
var HOST, TAB_GLYPHS;
|
|
@@ -16805,6 +16934,15 @@ function parseCursorLine(line) {
|
|
|
16805
16934
|
}
|
|
16806
16935
|
return [];
|
|
16807
16936
|
}
|
|
16937
|
+
function stderrNotice(raw) {
|
|
16938
|
+
const text = String(raw || "").trim();
|
|
16939
|
+
if (!text) return "";
|
|
16940
|
+
if (/ActionRequiredError/i.test(text) && /usage limit/i.test(text)) {
|
|
16941
|
+
return "Cursor usage limit reached \u2014 replies still land, but turns will keep stalling until the plan resets or is topped up";
|
|
16942
|
+
}
|
|
16943
|
+
if (/RetriableError/i.test(text)) return "";
|
|
16944
|
+
return /error|fatal/i.test(text) ? text : "";
|
|
16945
|
+
}
|
|
16808
16946
|
function feed(buffer, chunk) {
|
|
16809
16947
|
const combined = buffer + chunk;
|
|
16810
16948
|
const parts = combined.split("\n");
|
|
@@ -16844,43 +16982,70 @@ var init_cursor = __esm({
|
|
|
16844
16982
|
});
|
|
16845
16983
|
|
|
16846
16984
|
// cli/harness/render.ts
|
|
16985
|
+
function spinnerFrame(tick) {
|
|
16986
|
+
return SPINNER[Math.abs(tick) % SPINNER.length];
|
|
16987
|
+
}
|
|
16847
16988
|
function clip3(text, max) {
|
|
16848
16989
|
const value = String(text || "").replace(/\s+/g, " ").trim();
|
|
16990
|
+
if (max <= 1) return value;
|
|
16849
16991
|
return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
|
|
16850
16992
|
}
|
|
16993
|
+
function seconds(ms) {
|
|
16994
|
+
const total = Math.max(0, Math.round(ms / 1e3));
|
|
16995
|
+
if (total < 60) return total + "s";
|
|
16996
|
+
return Math.floor(total / 60) + "m" + String(total % 60).padStart(2, "0") + "s";
|
|
16997
|
+
}
|
|
16998
|
+
function statusLine(opts) {
|
|
16999
|
+
const hint = opts.hint ? " \xB7 " + opts.hint : "";
|
|
17000
|
+
const body = opts.text + " (" + seconds(opts.elapsedMs) + hint + ")";
|
|
17001
|
+
const width = Math.max(20, (opts.width || 100) - 4);
|
|
17002
|
+
return S2.think + " " + spinnerFrame(opts.tick) + " " + clip3(body, width) + S2.reset;
|
|
17003
|
+
}
|
|
16851
17004
|
function renderEvent(event, width = 100) {
|
|
16852
17005
|
const body = Math.max(30, width - 4);
|
|
16853
17006
|
switch (event.type) {
|
|
17007
|
+
// The workspace and both accounts are already in the session header, so
|
|
17008
|
+
// this line carries only what the header could not know before the harness
|
|
17009
|
+
// started: which model answered, and whether a subscription or a key paid.
|
|
16854
17010
|
case "session-start":
|
|
16855
17011
|
return [
|
|
16856
17012
|
"",
|
|
16857
|
-
S2.dim + " " +
|
|
17013
|
+
S2.dim + " " + clip3(event.model, body - 20) + (event.authSource === "login" ? " \xB7 subscription" : " \xB7 api key") + S2.reset,
|
|
16858
17014
|
""
|
|
16859
17015
|
];
|
|
16860
17016
|
case "user-message":
|
|
16861
17017
|
return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset, ""];
|
|
17018
|
+
// Live state, not transcript. The runner promotes these to the status line.
|
|
16862
17019
|
case "thinking":
|
|
16863
|
-
return [
|
|
16864
|
-
case "assistant-message":
|
|
16865
|
-
|
|
17020
|
+
return [];
|
|
17021
|
+
case "assistant-message": {
|
|
17022
|
+
const lines = wrap(event.text, body - 2);
|
|
17023
|
+
return [
|
|
17024
|
+
"",
|
|
17025
|
+
...lines.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset),
|
|
17026
|
+
""
|
|
17027
|
+
];
|
|
17028
|
+
}
|
|
16866
17029
|
case "tool-start": {
|
|
16867
|
-
const
|
|
16868
|
-
return [
|
|
17030
|
+
const label = TOOL_LABEL[event.kind] || TOOL_LABEL.other;
|
|
17031
|
+
return [
|
|
17032
|
+
S2.tool + " " + BULLET + " " + label + S2.reset + S2.dim + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
|
|
17033
|
+
];
|
|
16869
17034
|
}
|
|
16870
17035
|
case "tool-end": {
|
|
16871
17036
|
if (event.blocked) {
|
|
16872
17037
|
return [
|
|
16873
|
-
S2.blocked + "
|
|
16874
|
-
...wrap(event.reason, body - 6).map((line) => "
|
|
17038
|
+
S2.blocked + " " + BULLET + " Blocked" + S2.reset + S2.dim + " " + clip3(event.target, body - 14) + S2.reset,
|
|
17039
|
+
...wrap(event.reason, body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset)
|
|
16875
17040
|
];
|
|
16876
17041
|
}
|
|
16877
|
-
const
|
|
16878
|
-
const
|
|
16879
|
-
|
|
16880
|
-
return [mark + S2.reset + code + out];
|
|
17042
|
+
const detail = event.ok ? event.output.trim() ? clip3(event.output, body - 10) : "done" : "failed" + (event.exitCode === null ? "" : " (exit " + event.exitCode + ")");
|
|
17043
|
+
const tint = event.ok ? S2.ok : S2.blocked;
|
|
17044
|
+
return [tint + " " + ELBOW + " " + S2.reset + S2.dim + detail + S2.reset];
|
|
16881
17045
|
}
|
|
17046
|
+
// A clean finish needs no announcement: the prompt returning IS the signal.
|
|
16882
17047
|
case "turn-end":
|
|
16883
|
-
return ["", S2.
|
|
17048
|
+
return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
|
|
16884
17049
|
case "notice":
|
|
16885
17050
|
return [S2.dim + " " + clip3(event.text, body) + S2.reset];
|
|
16886
17051
|
default:
|
|
@@ -16903,7 +17068,7 @@ function wrap(text, width) {
|
|
|
16903
17068
|
if (line) lines.push(line);
|
|
16904
17069
|
return lines;
|
|
16905
17070
|
}
|
|
16906
|
-
var ESC2, S2,
|
|
17071
|
+
var ESC2, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
|
|
16907
17072
|
var init_render2 = __esm({
|
|
16908
17073
|
"cli/harness/render.ts"() {
|
|
16909
17074
|
"use strict";
|
|
@@ -16920,16 +17085,20 @@ var init_render2 = __esm({
|
|
|
16920
17085
|
blocked: ESC2 + "38;5;203m",
|
|
16921
17086
|
rule: ESC2 + "38;5;211m"
|
|
16922
17087
|
};
|
|
16923
|
-
|
|
16924
|
-
|
|
16925
|
-
|
|
16926
|
-
|
|
16927
|
-
|
|
16928
|
-
|
|
16929
|
-
|
|
16930
|
-
|
|
16931
|
-
|
|
16932
|
-
|
|
17088
|
+
CLEAR_LINE = "\r" + ESC2 + "2K";
|
|
17089
|
+
BULLET = "\u23FA";
|
|
17090
|
+
ELBOW = "\u23BF";
|
|
17091
|
+
SPINNER = ["\xB7", "\u2722", "\u2733", "\u2217", "\u273B", "\u273D"];
|
|
17092
|
+
TOOL_LABEL = {
|
|
17093
|
+
shell: "Shell",
|
|
17094
|
+
read: "Read",
|
|
17095
|
+
edit: "Edit",
|
|
17096
|
+
write: "Write",
|
|
17097
|
+
delete: "Delete",
|
|
17098
|
+
search: "Search",
|
|
17099
|
+
list: "List",
|
|
17100
|
+
todo: "Todo",
|
|
17101
|
+
other: "Tool"
|
|
16933
17102
|
};
|
|
16934
17103
|
}
|
|
16935
17104
|
});
|
|
@@ -16952,33 +17121,138 @@ function replayKey(event) {
|
|
|
16952
17121
|
}
|
|
16953
17122
|
function createTurnSink(opts) {
|
|
16954
17123
|
const events = [];
|
|
17124
|
+
const now = opts.now || (() => Date.now());
|
|
17125
|
+
const showPrompt = opts.showPrompt !== false;
|
|
16955
17126
|
let blocked = 0;
|
|
16956
|
-
let lastWasThinking = false;
|
|
16957
17127
|
let replaying = false;
|
|
16958
17128
|
const seen = /* @__PURE__ */ new Set();
|
|
16959
17129
|
let sawTurnEnd = false;
|
|
16960
17130
|
let sawAnswer = false;
|
|
16961
17131
|
let buffer = "";
|
|
17132
|
+
let reconnecting = false;
|
|
17133
|
+
let lastWasAnswer = false;
|
|
17134
|
+
let statusText = "";
|
|
17135
|
+
let statusKind = "progress";
|
|
17136
|
+
let statusShown = false;
|
|
17137
|
+
let tick = 0;
|
|
17138
|
+
const startedAt = now();
|
|
17139
|
+
let timer = null;
|
|
17140
|
+
const drawStatus = () => {
|
|
17141
|
+
if (!opts.animate || !statusText) return;
|
|
17142
|
+
opts.write(statusLine({
|
|
17143
|
+
tick,
|
|
17144
|
+
text: statusText,
|
|
17145
|
+
elapsedMs: now() - startedAt,
|
|
17146
|
+
width: opts.width,
|
|
17147
|
+
hint: "ctrl-c to interrupt"
|
|
17148
|
+
}));
|
|
17149
|
+
statusShown = true;
|
|
17150
|
+
};
|
|
17151
|
+
const clearStatus = () => {
|
|
17152
|
+
if (!statusShown) return;
|
|
17153
|
+
opts.write(CLEAR_LINE);
|
|
17154
|
+
statusShown = false;
|
|
17155
|
+
};
|
|
17156
|
+
const setStatus = (text, kind = "progress") => {
|
|
17157
|
+
statusText = text;
|
|
17158
|
+
statusKind = kind;
|
|
17159
|
+
if (!opts.animate) return;
|
|
17160
|
+
if (!timer) {
|
|
17161
|
+
timer = setInterval(() => {
|
|
17162
|
+
tick += 1;
|
|
17163
|
+
clearStatus();
|
|
17164
|
+
drawStatus();
|
|
17165
|
+
}, TICK_MS);
|
|
17166
|
+
timer.unref?.();
|
|
17167
|
+
}
|
|
17168
|
+
clearStatus();
|
|
17169
|
+
drawStatus();
|
|
17170
|
+
};
|
|
17171
|
+
const stopStatus = () => {
|
|
17172
|
+
statusText = "";
|
|
17173
|
+
statusKind = "progress";
|
|
17174
|
+
if (timer) {
|
|
17175
|
+
clearInterval(timer);
|
|
17176
|
+
timer = null;
|
|
17177
|
+
}
|
|
17178
|
+
clearStatus();
|
|
17179
|
+
};
|
|
17180
|
+
let gaveUp = false;
|
|
17181
|
+
let stall = null;
|
|
17182
|
+
const graceMs = opts.postAnswerGraceMs ?? 15e3;
|
|
17183
|
+
const disarmStall = () => {
|
|
17184
|
+
if (stall) {
|
|
17185
|
+
clearTimeout(stall);
|
|
17186
|
+
stall = null;
|
|
17187
|
+
}
|
|
17188
|
+
};
|
|
17189
|
+
const giveUp = () => {
|
|
17190
|
+
if (gaveUp) return;
|
|
17191
|
+
gaveUp = true;
|
|
17192
|
+
disarmStall();
|
|
17193
|
+
opts.onGiveUp?.();
|
|
17194
|
+
};
|
|
17195
|
+
const armStall = () => {
|
|
17196
|
+
disarmStall();
|
|
17197
|
+
stall = setTimeout(giveUp, graceMs);
|
|
17198
|
+
stall.unref?.();
|
|
17199
|
+
};
|
|
17200
|
+
const settle = () => {
|
|
17201
|
+
reconnecting = false;
|
|
17202
|
+
if (statusKind === "retry") setStatus("Thinking");
|
|
17203
|
+
};
|
|
16962
17204
|
const emit2 = (event) => {
|
|
16963
17205
|
events.push(event);
|
|
16964
17206
|
if (event.type === "tool-end" && event.blocked) blocked += 1;
|
|
16965
17207
|
opts.onEvent?.(event);
|
|
17208
|
+
if (event.type === "notice" && event.kind === "retry") {
|
|
17209
|
+
reconnecting = true;
|
|
17210
|
+
if (sawAnswer && lastWasAnswer) {
|
|
17211
|
+
setStatus("finishing up", "retry");
|
|
17212
|
+
armStall();
|
|
17213
|
+
} else {
|
|
17214
|
+
setStatus(event.text, "retry");
|
|
17215
|
+
}
|
|
17216
|
+
return;
|
|
17217
|
+
}
|
|
16966
17218
|
if (event.type === "thinking") {
|
|
16967
|
-
if (
|
|
16968
|
-
|
|
16969
|
-
}
|
|
16970
|
-
|
|
17219
|
+
if (!reconnecting) setStatus("Thinking");
|
|
17220
|
+
return;
|
|
17221
|
+
}
|
|
17222
|
+
if (event.type === "user-message" && !showPrompt) return;
|
|
17223
|
+
if (event.type === "session-start" && opts.showHeader === false) return;
|
|
17224
|
+
if (event.type === "assistant-message" || event.type === "tool-end") settle();
|
|
17225
|
+
if (event.type === "tool-start") {
|
|
17226
|
+
reconnecting = false;
|
|
17227
|
+
setStatus("Running " + event.kind);
|
|
16971
17228
|
}
|
|
17229
|
+
if (event.type === "turn-end") stopStatus();
|
|
16972
17230
|
const rendered = renderEvent(event, opts.width);
|
|
16973
|
-
if (rendered.length)
|
|
17231
|
+
if (!rendered.length) return;
|
|
17232
|
+
clearStatus();
|
|
17233
|
+
opts.write(rendered.join("\n") + "\n");
|
|
17234
|
+
drawStatus();
|
|
16974
17235
|
};
|
|
16975
17236
|
const take = (event) => {
|
|
16976
|
-
|
|
17237
|
+
const isRetry = event.type === "notice" && event.kind === "retry";
|
|
17238
|
+
if (!isRetry) disarmStall();
|
|
17239
|
+
if (isRetry) {
|
|
17240
|
+
if (reconnecting && sawAnswer && lastWasAnswer) {
|
|
17241
|
+
giveUp();
|
|
17242
|
+
return;
|
|
17243
|
+
}
|
|
17244
|
+
replaying = true;
|
|
17245
|
+
}
|
|
16977
17246
|
const key = replayKey(event);
|
|
16978
17247
|
if (key) {
|
|
16979
|
-
if (replaying && seen.has(key))
|
|
17248
|
+
if (replaying && seen.has(key)) {
|
|
17249
|
+
lastWasAnswer = event.type === "assistant-message";
|
|
17250
|
+
settle();
|
|
17251
|
+
return;
|
|
17252
|
+
}
|
|
16980
17253
|
seen.add(key);
|
|
16981
17254
|
}
|
|
17255
|
+
if (key || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
|
|
16982
17256
|
if (event.type === "turn-end") sawTurnEnd = true;
|
|
16983
17257
|
if (event.type === "assistant-message") sawAnswer = true;
|
|
16984
17258
|
emit2(event);
|
|
@@ -16992,57 +17266,159 @@ function createTurnSink(opts) {
|
|
|
16992
17266
|
notice(text) {
|
|
16993
17267
|
emit2({ type: "notice", text });
|
|
16994
17268
|
},
|
|
16995
|
-
finish(exit) {
|
|
17269
|
+
finish(exit, interrupted = false) {
|
|
17270
|
+
disarmStall();
|
|
17271
|
+
stopStatus();
|
|
16996
17272
|
if (!sawTurnEnd) {
|
|
16997
|
-
if (
|
|
16998
|
-
emit2({
|
|
16999
|
-
|
|
17000
|
-
|
|
17001
|
-
|
|
17273
|
+
if (interrupted) {
|
|
17274
|
+
emit2({ type: "notice", text: "interrupted" });
|
|
17275
|
+
emit2({ type: "turn-end", ok: true, text: "" });
|
|
17276
|
+
} else {
|
|
17277
|
+
if (exit !== 0 && !sawAnswer) {
|
|
17278
|
+
emit2({ type: "notice", text: "cursor-agent exited " + exit + " without completing the turn" });
|
|
17279
|
+
}
|
|
17280
|
+
emit2({ type: "turn-end", ok: sawAnswer, text: "" });
|
|
17002
17281
|
}
|
|
17003
|
-
emit2({ type: "turn-end", ok: sawAnswer, text: "" });
|
|
17004
17282
|
}
|
|
17005
|
-
|
|
17283
|
+
stopStatus();
|
|
17284
|
+
return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit };
|
|
17006
17285
|
}
|
|
17007
17286
|
};
|
|
17008
17287
|
}
|
|
17009
17288
|
async function runCursorTurn(opts) {
|
|
17010
17289
|
const write2 = opts.write || ((text) => process.stdout.write(text));
|
|
17011
17290
|
const width = opts.width || Number(process.stdout.columns || 100);
|
|
17012
|
-
|
|
17291
|
+
let stopHarness = () => {
|
|
17292
|
+
};
|
|
17293
|
+
const sink = createTurnSink({
|
|
17294
|
+
write: write2,
|
|
17295
|
+
onEvent: opts.onEvent,
|
|
17296
|
+
width,
|
|
17297
|
+
animate: Boolean(process.stdout.isTTY),
|
|
17298
|
+
showPrompt: opts.showPrompt,
|
|
17299
|
+
showHeader: opts.showHeader,
|
|
17300
|
+
onGiveUp: () => stopHarness()
|
|
17301
|
+
});
|
|
17013
17302
|
return new Promise((resolve7) => {
|
|
17014
17303
|
const child = spawn9("cursor-agent", cursorArgs(opts.prompt), {
|
|
17015
17304
|
cwd: opts.cwd,
|
|
17016
17305
|
stdio: ["ignore", "pipe", "pipe"]
|
|
17017
17306
|
});
|
|
17307
|
+
stopHarness = () => {
|
|
17308
|
+
child.kill();
|
|
17309
|
+
const hard = setTimeout(() => child.kill("SIGKILL"), 3e3);
|
|
17310
|
+
hard.unref?.();
|
|
17311
|
+
child.once("close", () => clearTimeout(hard));
|
|
17312
|
+
};
|
|
17313
|
+
let interrupted = false;
|
|
17314
|
+
const onAbort = () => {
|
|
17315
|
+
interrupted = true;
|
|
17316
|
+
stopHarness();
|
|
17317
|
+
};
|
|
17318
|
+
if (opts.signal?.aborted) onAbort();
|
|
17319
|
+
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
17018
17320
|
child.stdout.on("data", (chunk) => sink.chunk(chunk.toString("utf8")));
|
|
17321
|
+
let lastNote = "";
|
|
17019
17322
|
child.stderr.on("data", (chunk) => {
|
|
17020
|
-
const
|
|
17021
|
-
if (
|
|
17323
|
+
const note = stderrNotice(chunk.toString("utf8"));
|
|
17324
|
+
if (note && note !== lastNote) {
|
|
17325
|
+
lastNote = note;
|
|
17326
|
+
sink.notice(note);
|
|
17327
|
+
}
|
|
17328
|
+
});
|
|
17329
|
+
child.on("close", (code) => {
|
|
17330
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
17331
|
+
resolve7(sink.finish(code ?? 0, interrupted));
|
|
17022
17332
|
});
|
|
17023
|
-
child.on("close", (code) => resolve7(sink.finish(code ?? 0)));
|
|
17024
17333
|
child.on("error", (error) => {
|
|
17025
17334
|
sink.notice("failed to start cursor-agent: " + String(error));
|
|
17026
|
-
resolve7(sink.finish(1));
|
|
17335
|
+
resolve7(sink.finish(1, interrupted));
|
|
17027
17336
|
});
|
|
17028
17337
|
});
|
|
17029
17338
|
}
|
|
17339
|
+
var TICK_MS;
|
|
17030
17340
|
var init_run = __esm({
|
|
17031
17341
|
"cli/harness/run.ts"() {
|
|
17032
17342
|
"use strict";
|
|
17033
17343
|
init_cursor();
|
|
17034
17344
|
init_render2();
|
|
17345
|
+
TICK_MS = 120;
|
|
17346
|
+
}
|
|
17347
|
+
});
|
|
17348
|
+
|
|
17349
|
+
// cli/harness/identity.ts
|
|
17350
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
17351
|
+
import { homedir as homedir38 } from "os";
|
|
17352
|
+
function shortenPath(path, home = homedir38()) {
|
|
17353
|
+
const value = String(path || "");
|
|
17354
|
+
if (home && value === home) return "~";
|
|
17355
|
+
if (home && value.startsWith(home + "/")) return "~" + value.slice(home.length);
|
|
17356
|
+
return value;
|
|
17357
|
+
}
|
|
17358
|
+
function parseCursorAccount(output) {
|
|
17359
|
+
const match = String(output || "").match(/logged in as\s+(\S+)/i);
|
|
17360
|
+
return match ? match[1].trim() : "";
|
|
17361
|
+
}
|
|
17362
|
+
function cursorAccount() {
|
|
17363
|
+
try {
|
|
17364
|
+
const out = execFileSync5("cursor-agent", ["status"], {
|
|
17365
|
+
encoding: "utf8",
|
|
17366
|
+
timeout: 5e3,
|
|
17367
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
17368
|
+
});
|
|
17369
|
+
return parseCursorAccount(out);
|
|
17370
|
+
} catch {
|
|
17371
|
+
return "";
|
|
17372
|
+
}
|
|
17373
|
+
}
|
|
17374
|
+
function synkroAccount() {
|
|
17375
|
+
let email = "";
|
|
17376
|
+
try {
|
|
17377
|
+
email = String(getUserInfo().email || "");
|
|
17378
|
+
} catch {
|
|
17379
|
+
return { email: "", needsLogin: false };
|
|
17380
|
+
}
|
|
17381
|
+
let needsLogin = false;
|
|
17382
|
+
try {
|
|
17383
|
+
needsLogin = isTokenExpired() && !loadCredentials()?.refresh_token;
|
|
17384
|
+
} catch {
|
|
17385
|
+
}
|
|
17386
|
+
return { email, needsLogin };
|
|
17387
|
+
}
|
|
17388
|
+
function readIdentity(harness) {
|
|
17389
|
+
const synkro = synkroAccount();
|
|
17390
|
+
return {
|
|
17391
|
+
harness: harness === "cursor" ? cursorAccount() : "",
|
|
17392
|
+
synkro: synkro.email,
|
|
17393
|
+
needsLogin: synkro.needsLogin
|
|
17394
|
+
};
|
|
17395
|
+
}
|
|
17396
|
+
function identityHeader(opts) {
|
|
17397
|
+
const lines = ["", S2.bold + " " + shortenPath(opts.cwd, opts.home) + S2.reset];
|
|
17398
|
+
const label = (name, value, note = "") => S2.dim + " " + name.padEnd(7) + S2.reset + S2.think + value + S2.reset + note;
|
|
17399
|
+
if (opts.identity.harness) lines.push(label(opts.harness, opts.identity.harness));
|
|
17400
|
+
if (opts.identity.synkro) {
|
|
17401
|
+
lines.push(label("synkro", opts.identity.synkro, opts.identity.needsLogin ? S2.blocked + " \xB7 session ended, run synkro login" + S2.reset : ""));
|
|
17402
|
+
}
|
|
17403
|
+
lines.push("");
|
|
17404
|
+
return lines;
|
|
17405
|
+
}
|
|
17406
|
+
var init_identity2 = __esm({
|
|
17407
|
+
"cli/harness/identity.ts"() {
|
|
17408
|
+
"use strict";
|
|
17409
|
+
init_auth();
|
|
17410
|
+
init_render2();
|
|
17035
17411
|
}
|
|
17036
17412
|
});
|
|
17037
17413
|
|
|
17038
17414
|
// cli/harness/session.ts
|
|
17039
17415
|
import { createInterface as createInterface5 } from "readline";
|
|
17040
|
-
async function runOnce(harness, cwd, prompt) {
|
|
17416
|
+
async function runOnce(harness, cwd, prompt, echoPrompt = true, showHeader = true, signal) {
|
|
17041
17417
|
if (harness !== "cursor") {
|
|
17042
17418
|
process.stdout.write(S2.dim + " " + harness + " sessions are not embedded yet\n" + S2.reset);
|
|
17043
17419
|
return 1;
|
|
17044
17420
|
}
|
|
17045
|
-
const result = await runCursorTurn({ prompt, cwd });
|
|
17421
|
+
const result = await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal });
|
|
17046
17422
|
if (result.blocked > 0) {
|
|
17047
17423
|
process.stdout.write(
|
|
17048
17424
|
S2.blocked + " " + result.blocked + " action" + (result.blocked === 1 ? "" : "s") + " blocked by policy" + S2.reset + "\n"
|
|
@@ -17052,31 +17428,46 @@ async function runOnce(harness, cwd, prompt) {
|
|
|
17052
17428
|
}
|
|
17053
17429
|
async function runGovernedSession(harness, cwd, prompt) {
|
|
17054
17430
|
if (prompt) return runOnce(harness, cwd, prompt);
|
|
17055
|
-
process.stdout.write(
|
|
17431
|
+
process.stdout.write(identityHeader({ cwd, harness, identity: readIdentity(harness) }).join("\n") + "\n");
|
|
17056
17432
|
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
17057
|
-
const ask3 = () => new Promise((resolve7) =>
|
|
17433
|
+
const ask3 = () => new Promise((resolve7) => {
|
|
17434
|
+
const onClose = () => resolve7(null);
|
|
17435
|
+
rl.once("close", onClose);
|
|
17436
|
+
rl.question(S2.user + " \u276F " + S2.reset, (answer) => {
|
|
17437
|
+
rl.removeListener("close", onClose);
|
|
17438
|
+
resolve7(answer);
|
|
17439
|
+
});
|
|
17440
|
+
});
|
|
17441
|
+
let turn = null;
|
|
17442
|
+
rl.on("SIGINT", () => {
|
|
17443
|
+
if (turn) {
|
|
17444
|
+
turn.abort();
|
|
17445
|
+
return;
|
|
17446
|
+
}
|
|
17447
|
+
process.stdout.write("\n");
|
|
17448
|
+
rl.close();
|
|
17449
|
+
});
|
|
17450
|
+
let first = true;
|
|
17058
17451
|
for (; ; ) {
|
|
17059
|
-
const
|
|
17452
|
+
const answer = await ask3();
|
|
17453
|
+
if (answer === null) break;
|
|
17454
|
+
const line = answer.trim();
|
|
17060
17455
|
if (!line) continue;
|
|
17061
17456
|
if (line === "exit" || line === "quit") break;
|
|
17062
|
-
|
|
17457
|
+
turn = new AbortController();
|
|
17458
|
+
await runOnce(harness, cwd, line, false, first, turn.signal);
|
|
17459
|
+
turn = null;
|
|
17460
|
+
first = false;
|
|
17063
17461
|
}
|
|
17064
17462
|
rl.close();
|
|
17065
17463
|
return 0;
|
|
17066
17464
|
}
|
|
17067
|
-
var BANNER;
|
|
17068
17465
|
var init_session = __esm({
|
|
17069
17466
|
"cli/harness/session.ts"() {
|
|
17070
17467
|
"use strict";
|
|
17071
17468
|
init_run();
|
|
17469
|
+
init_identity2();
|
|
17072
17470
|
init_render2();
|
|
17073
|
-
BANNER = [
|
|
17074
|
-
"",
|
|
17075
|
-
S2.bold + " synkro" + S2.reset + S2.dim + " governed session" + S2.reset,
|
|
17076
|
-
S2.dim + " every tool call passes Synkro policy before it runs" + S2.reset,
|
|
17077
|
-
S2.dim + " ctrl-c to leave" + S2.reset,
|
|
17078
|
-
""
|
|
17079
|
-
];
|
|
17080
17471
|
}
|
|
17081
17472
|
});
|
|
17082
17473
|
|
|
@@ -17269,7 +17660,7 @@ __export(linear_exports, {
|
|
|
17269
17660
|
linearCommand: () => linearCommand
|
|
17270
17661
|
});
|
|
17271
17662
|
import { readFileSync as readFileSync33 } from "fs";
|
|
17272
|
-
import { homedir as
|
|
17663
|
+
import { homedir as homedir39 } from "os";
|
|
17273
17664
|
import { join as join37 } from "path";
|
|
17274
17665
|
function mcpJwt() {
|
|
17275
17666
|
try {
|
|
@@ -17312,7 +17703,7 @@ var SYNKRO_DIR14, PORT2, BASE;
|
|
|
17312
17703
|
var init_linear = __esm({
|
|
17313
17704
|
"cli/commands/linear.ts"() {
|
|
17314
17705
|
"use strict";
|
|
17315
|
-
SYNKRO_DIR14 = join37(
|
|
17706
|
+
SYNKRO_DIR14 = join37(homedir39(), ".synkro");
|
|
17316
17707
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
17317
17708
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
17318
17709
|
}
|
|
@@ -17461,10 +17852,10 @@ var init_cveReachability = __esm({
|
|
|
17461
17852
|
});
|
|
17462
17853
|
|
|
17463
17854
|
// cli/reachability/reachabilityScan.ts
|
|
17464
|
-
import { spawnSync as spawnSync12, execFileSync as
|
|
17855
|
+
import { spawnSync as spawnSync12, execFileSync as execFileSync6 } from "child_process";
|
|
17465
17856
|
import { readFileSync as readFileSync35, writeFileSync as writeFileSync27, existsSync as existsSync38, readdirSync as readdirSync10 } from "fs";
|
|
17466
17857
|
import { join as join38 } from "path";
|
|
17467
|
-
import { homedir as
|
|
17858
|
+
import { homedir as homedir40 } from "os";
|
|
17468
17859
|
import { createRequire } from "module";
|
|
17469
17860
|
function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
17470
17861
|
const SKIP2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
|
|
@@ -17588,7 +17979,7 @@ function findEntries(repoRoot3) {
|
|
|
17588
17979
|
}
|
|
17589
17980
|
function currentCommit(repoRoot3) {
|
|
17590
17981
|
try {
|
|
17591
|
-
return
|
|
17982
|
+
return execFileSync6("git", ["rev-parse", "HEAD"], { cwd: repoRoot3, encoding: "utf8" }).trim();
|
|
17592
17983
|
} catch {
|
|
17593
17984
|
return "";
|
|
17594
17985
|
}
|
|
@@ -17712,7 +18103,7 @@ var init_reachabilityScan = __esm({
|
|
|
17712
18103
|
"use strict";
|
|
17713
18104
|
init_cveReachability();
|
|
17714
18105
|
require2 = createRequire(import.meta.url);
|
|
17715
|
-
REACHABILITY_PATH = join38(
|
|
18106
|
+
REACHABILITY_PATH = join38(homedir40(), ".synkro", "reachability.json");
|
|
17716
18107
|
}
|
|
17717
18108
|
});
|
|
17718
18109
|
|
|
@@ -17723,8 +18114,8 @@ __export(reachabilityScan_exports, {
|
|
|
17723
18114
|
});
|
|
17724
18115
|
import { readFileSync as readFileSync36, existsSync as existsSync39 } from "fs";
|
|
17725
18116
|
import { join as join39 } from "path";
|
|
17726
|
-
import { homedir as
|
|
17727
|
-
import { execFileSync as
|
|
18117
|
+
import { homedir as homedir41 } from "os";
|
|
18118
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
17728
18119
|
function readConfigEnv4() {
|
|
17729
18120
|
const p = join39(SYNKRO_DIR15, "config.env");
|
|
17730
18121
|
if (!existsSync39(p)) return {};
|
|
@@ -17739,7 +18130,7 @@ function readConfigEnv4() {
|
|
|
17739
18130
|
}
|
|
17740
18131
|
function repoRoot2() {
|
|
17741
18132
|
try {
|
|
17742
|
-
return
|
|
18133
|
+
return execFileSync7("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
|
|
17743
18134
|
} catch {
|
|
17744
18135
|
return process.cwd();
|
|
17745
18136
|
}
|
|
@@ -17747,7 +18138,7 @@ function repoRoot2() {
|
|
|
17747
18138
|
function repoSlug(root) {
|
|
17748
18139
|
const run2 = (a) => {
|
|
17749
18140
|
try {
|
|
17750
|
-
return
|
|
18141
|
+
return execFileSync7("git", a, { encoding: "utf-8" }).trim();
|
|
17751
18142
|
} catch {
|
|
17752
18143
|
return "";
|
|
17753
18144
|
}
|
|
@@ -17797,7 +18188,7 @@ var init_reachabilityScan2 = __esm({
|
|
|
17797
18188
|
"cli/commands/reachabilityScan.ts"() {
|
|
17798
18189
|
"use strict";
|
|
17799
18190
|
init_reachabilityScan();
|
|
17800
|
-
SYNKRO_DIR15 = join39(
|
|
18191
|
+
SYNKRO_DIR15 = join39(homedir41(), ".synkro");
|
|
17801
18192
|
}
|
|
17802
18193
|
});
|
|
17803
18194
|
|
|
@@ -17929,7 +18320,7 @@ __export(config_exports, {
|
|
|
17929
18320
|
});
|
|
17930
18321
|
import { readFileSync as readFileSync37, writeFileSync as writeFileSync28, existsSync as existsSync40 } from "fs";
|
|
17931
18322
|
import { join as join40 } from "path";
|
|
17932
|
-
import { homedir as
|
|
18323
|
+
import { homedir as homedir42 } from "os";
|
|
17933
18324
|
function readConfigEnv5() {
|
|
17934
18325
|
if (!existsSync40(CONFIG_PATH9)) return {};
|
|
17935
18326
|
const out = {};
|
|
@@ -18115,7 +18506,7 @@ var init_config = __esm({
|
|
|
18115
18506
|
"use strict";
|
|
18116
18507
|
init_stub();
|
|
18117
18508
|
init_optout();
|
|
18118
|
-
SYNKRO_DIR16 = join40(
|
|
18509
|
+
SYNKRO_DIR16 = join40(homedir42(), ".synkro");
|
|
18119
18510
|
CONFIG_PATH9 = join40(SYNKRO_DIR16, "config.env");
|
|
18120
18511
|
}
|
|
18121
18512
|
});
|
|
@@ -18307,10 +18698,10 @@ Usage:
|
|
|
18307
18698
|
// cli/inventory/identity.ts
|
|
18308
18699
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
18309
18700
|
import { existsSync as existsSync41, mkdirSync as mkdirSync24, readFileSync as readFileSync38, renameSync as renameSync9, writeFileSync as writeFileSync29 } from "fs";
|
|
18310
|
-
import { homedir as
|
|
18701
|
+
import { homedir as homedir43 } from "os";
|
|
18311
18702
|
import { dirname as dirname12, join as join41 } from "path";
|
|
18312
18703
|
function operationalIdentityPath() {
|
|
18313
|
-
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join41(
|
|
18704
|
+
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join41(homedir43(), ".synkro", "installation.json");
|
|
18314
18705
|
}
|
|
18315
18706
|
function validIdentity(value) {
|
|
18316
18707
|
if (!value || typeof value !== "object") return false;
|
|
@@ -18342,7 +18733,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
|
18342
18733
|
return identity;
|
|
18343
18734
|
}
|
|
18344
18735
|
var UUID_RE, cached4;
|
|
18345
|
-
var
|
|
18736
|
+
var init_identity3 = __esm({
|
|
18346
18737
|
"cli/inventory/identity.ts"() {
|
|
18347
18738
|
"use strict";
|
|
18348
18739
|
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -18358,7 +18749,7 @@ import {
|
|
|
18358
18749
|
readdirSync as readdirSync11,
|
|
18359
18750
|
statSync as statSync5
|
|
18360
18751
|
} from "fs";
|
|
18361
|
-
import { arch, homedir as
|
|
18752
|
+
import { arch, homedir as homedir44, hostname as hostname2, platform as platform6, release as release2 } from "os";
|
|
18362
18753
|
import { basename as basename3, join as join42, relative, resolve as resolve5 } from "path";
|
|
18363
18754
|
import { fileURLToPath } from "url";
|
|
18364
18755
|
function sha256(value) {
|
|
@@ -18369,7 +18760,7 @@ function pseudonymousHostnameHash(installationId, host) {
|
|
|
18369
18760
|
}
|
|
18370
18761
|
function cliVersion() {
|
|
18371
18762
|
try {
|
|
18372
|
-
return "1.10.
|
|
18763
|
+
return "1.10.6";
|
|
18373
18764
|
} catch {
|
|
18374
18765
|
return "0.0.0";
|
|
18375
18766
|
}
|
|
@@ -18764,7 +19155,7 @@ function harnessSnapshot(agent) {
|
|
|
18764
19155
|
};
|
|
18765
19156
|
}
|
|
18766
19157
|
function collectOperationalInventory(options = {}) {
|
|
18767
|
-
const home = options.homeDir ??
|
|
19158
|
+
const home = options.homeDir ?? homedir44();
|
|
18768
19159
|
const detected = options.detectedAgents ?? detectAgents();
|
|
18769
19160
|
const identity = getOperationalInstallationIdentity(options.identityPath);
|
|
18770
19161
|
const targetPlatform = options.platformName ?? platform6();
|
|
@@ -18876,7 +19267,7 @@ var init_collector = __esm({
|
|
|
18876
19267
|
init_ccHookConfig();
|
|
18877
19268
|
init_cursorHookConfig();
|
|
18878
19269
|
init_codexHookConfig();
|
|
18879
|
-
|
|
19270
|
+
init_identity3();
|
|
18880
19271
|
}
|
|
18881
19272
|
});
|
|
18882
19273
|
|
|
@@ -18900,10 +19291,10 @@ import {
|
|
|
18900
19291
|
renameSync as renameSync10,
|
|
18901
19292
|
writeFileSync as writeFileSync30
|
|
18902
19293
|
} from "fs";
|
|
18903
|
-
import { homedir as
|
|
19294
|
+
import { homedir as homedir45 } from "os";
|
|
18904
19295
|
import { dirname as dirname13, join as join43 } from "path";
|
|
18905
19296
|
function syncStatePath() {
|
|
18906
|
-
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join43(
|
|
19297
|
+
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join43(homedir45(), ".synkro", "inventory-sync.json");
|
|
18907
19298
|
}
|
|
18908
19299
|
function readState(path = syncStatePath()) {
|
|
18909
19300
|
try {
|
|
@@ -18930,7 +19321,7 @@ function shouldSyncInventory(state, now = Date.now(), target) {
|
|
|
18930
19321
|
return !Number.isFinite(lastAttempt) || lastAttempt <= 0 || now - lastAttempt >= FAILURE_RETRY_MS;
|
|
18931
19322
|
}
|
|
18932
19323
|
function readConfig() {
|
|
18933
|
-
const path = join43(
|
|
19324
|
+
const path = join43(homedir45(), ".synkro", "config.env");
|
|
18934
19325
|
const out = {};
|
|
18935
19326
|
try {
|
|
18936
19327
|
for (const rawLine of readFileSync40(path, "utf8").split("\n")) {
|
|
@@ -18972,7 +19363,7 @@ function resolveInventoryGateway(raw) {
|
|
|
18972
19363
|
}
|
|
18973
19364
|
async function loadToken() {
|
|
18974
19365
|
try {
|
|
18975
|
-
const durable = readFileSync40(join43(
|
|
19366
|
+
const durable = readFileSync40(join43(homedir45(), ".synkro", ".mcp-jwt"), "utf8").trim();
|
|
18976
19367
|
if (durable) return durable;
|
|
18977
19368
|
} catch {
|
|
18978
19369
|
}
|
|
@@ -19139,7 +19530,7 @@ var subArgs = args.slice(1);
|
|
|
19139
19530
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
19140
19531
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "inventory-sync", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
19141
19532
|
function printVersion() {
|
|
19142
|
-
console.log("1.10.
|
|
19533
|
+
console.log("1.10.6");
|
|
19143
19534
|
}
|
|
19144
19535
|
function printHelp2() {
|
|
19145
19536
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|