@synkro-sh/cli 1.10.4 → 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 +589 -141
- 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;
|
|
@@ -16705,11 +16834,14 @@ var init_events = __esm({
|
|
|
16705
16834
|
});
|
|
16706
16835
|
|
|
16707
16836
|
// cli/harness/cursor.ts
|
|
16837
|
+
function unwrapReplay(text) {
|
|
16838
|
+
return text.replace(/<\/?user_query>/gi, "").trim();
|
|
16839
|
+
}
|
|
16708
16840
|
function textOf(message) {
|
|
16709
16841
|
const content = message?.content;
|
|
16710
|
-
if (typeof content === "string") return content
|
|
16842
|
+
if (typeof content === "string") return unwrapReplay(content);
|
|
16711
16843
|
if (!Array.isArray(content)) return "";
|
|
16712
|
-
return content.filter((part) => part && (part.type === "text" || typeof part.text === "string")).map((part) => String(part.text || "")).join("")
|
|
16844
|
+
return unwrapReplay(content.filter((part) => part && (part.type === "text" || typeof part.text === "string")).map((part) => String(part.text || "")).join(""));
|
|
16713
16845
|
}
|
|
16714
16846
|
function toolPayload(toolCall) {
|
|
16715
16847
|
if (!toolCall) return { kind: "other", body: {} };
|
|
@@ -16784,6 +16916,15 @@ function parseCursorLine(line) {
|
|
|
16784
16916
|
}
|
|
16785
16917
|
return [];
|
|
16786
16918
|
}
|
|
16919
|
+
if (type === "retry") {
|
|
16920
|
+
if (subtype !== "starting") return [];
|
|
16921
|
+
const attempt = Number(frame.attempt || 0);
|
|
16922
|
+
return [{
|
|
16923
|
+
type: "notice",
|
|
16924
|
+
kind: "retry",
|
|
16925
|
+
text: "connection dropped, resuming" + (attempt ? " (attempt " + attempt + ")" : "") + "\u2026"
|
|
16926
|
+
}];
|
|
16927
|
+
}
|
|
16787
16928
|
if (type === "result") {
|
|
16788
16929
|
return [{
|
|
16789
16930
|
type: "turn-end",
|
|
@@ -16793,6 +16934,15 @@ function parseCursorLine(line) {
|
|
|
16793
16934
|
}
|
|
16794
16935
|
return [];
|
|
16795
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
|
+
}
|
|
16796
16946
|
function feed(buffer, chunk) {
|
|
16797
16947
|
const combined = buffer + chunk;
|
|
16798
16948
|
const parts = combined.split("\n");
|
|
@@ -16832,43 +16982,70 @@ var init_cursor = __esm({
|
|
|
16832
16982
|
});
|
|
16833
16983
|
|
|
16834
16984
|
// cli/harness/render.ts
|
|
16985
|
+
function spinnerFrame(tick) {
|
|
16986
|
+
return SPINNER[Math.abs(tick) % SPINNER.length];
|
|
16987
|
+
}
|
|
16835
16988
|
function clip3(text, max) {
|
|
16836
16989
|
const value = String(text || "").replace(/\s+/g, " ").trim();
|
|
16990
|
+
if (max <= 1) return value;
|
|
16837
16991
|
return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
|
|
16838
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
|
+
}
|
|
16839
17004
|
function renderEvent(event, width = 100) {
|
|
16840
17005
|
const body = Math.max(30, width - 4);
|
|
16841
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.
|
|
16842
17010
|
case "session-start":
|
|
16843
17011
|
return [
|
|
16844
17012
|
"",
|
|
16845
|
-
S2.dim + " " +
|
|
17013
|
+
S2.dim + " " + clip3(event.model, body - 20) + (event.authSource === "login" ? " \xB7 subscription" : " \xB7 api key") + S2.reset,
|
|
16846
17014
|
""
|
|
16847
17015
|
];
|
|
16848
17016
|
case "user-message":
|
|
16849
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.
|
|
16850
17019
|
case "thinking":
|
|
16851
|
-
return [
|
|
16852
|
-
case "assistant-message":
|
|
16853
|
-
|
|
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
|
+
}
|
|
16854
17029
|
case "tool-start": {
|
|
16855
|
-
const
|
|
16856
|
-
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
|
+
];
|
|
16857
17034
|
}
|
|
16858
17035
|
case "tool-end": {
|
|
16859
17036
|
if (event.blocked) {
|
|
16860
17037
|
return [
|
|
16861
|
-
S2.blocked + "
|
|
16862
|
-
...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)
|
|
16863
17040
|
];
|
|
16864
17041
|
}
|
|
16865
|
-
const
|
|
16866
|
-
const
|
|
16867
|
-
|
|
16868
|
-
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];
|
|
16869
17045
|
}
|
|
17046
|
+
// A clean finish needs no announcement: the prompt returning IS the signal.
|
|
16870
17047
|
case "turn-end":
|
|
16871
|
-
return ["", S2.
|
|
17048
|
+
return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
|
|
16872
17049
|
case "notice":
|
|
16873
17050
|
return [S2.dim + " " + clip3(event.text, body) + S2.reset];
|
|
16874
17051
|
default:
|
|
@@ -16891,7 +17068,7 @@ function wrap(text, width) {
|
|
|
16891
17068
|
if (line) lines.push(line);
|
|
16892
17069
|
return lines;
|
|
16893
17070
|
}
|
|
16894
|
-
var ESC2, S2,
|
|
17071
|
+
var ESC2, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
|
|
16895
17072
|
var init_render2 = __esm({
|
|
16896
17073
|
"cli/harness/render.ts"() {
|
|
16897
17074
|
"use strict";
|
|
@@ -16908,84 +17085,340 @@ var init_render2 = __esm({
|
|
|
16908
17085
|
blocked: ESC2 + "38;5;203m",
|
|
16909
17086
|
rule: ESC2 + "38;5;211m"
|
|
16910
17087
|
};
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
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"
|
|
16921
17102
|
};
|
|
16922
17103
|
}
|
|
16923
17104
|
});
|
|
16924
17105
|
|
|
16925
17106
|
// cli/harness/run.ts
|
|
16926
17107
|
import { spawn as spawn9 } from "child_process";
|
|
17108
|
+
function replayKey(event) {
|
|
17109
|
+
switch (event.type) {
|
|
17110
|
+
case "user-message":
|
|
17111
|
+
return "u:" + event.text;
|
|
17112
|
+
case "assistant-message":
|
|
17113
|
+
return "a:" + event.text;
|
|
17114
|
+
case "tool-start":
|
|
17115
|
+
return "s:" + event.id;
|
|
17116
|
+
case "tool-end":
|
|
17117
|
+
return "e:" + event.id;
|
|
17118
|
+
default:
|
|
17119
|
+
return "";
|
|
17120
|
+
}
|
|
17121
|
+
}
|
|
17122
|
+
function createTurnSink(opts) {
|
|
17123
|
+
const events = [];
|
|
17124
|
+
const now = opts.now || (() => Date.now());
|
|
17125
|
+
const showPrompt = opts.showPrompt !== false;
|
|
17126
|
+
let blocked = 0;
|
|
17127
|
+
let replaying = false;
|
|
17128
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17129
|
+
let sawTurnEnd = false;
|
|
17130
|
+
let sawAnswer = false;
|
|
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
|
+
};
|
|
17204
|
+
const emit2 = (event) => {
|
|
17205
|
+
events.push(event);
|
|
17206
|
+
if (event.type === "tool-end" && event.blocked) blocked += 1;
|
|
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
|
+
}
|
|
17218
|
+
if (event.type === "thinking") {
|
|
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);
|
|
17228
|
+
}
|
|
17229
|
+
if (event.type === "turn-end") stopStatus();
|
|
17230
|
+
const rendered = renderEvent(event, opts.width);
|
|
17231
|
+
if (!rendered.length) return;
|
|
17232
|
+
clearStatus();
|
|
17233
|
+
opts.write(rendered.join("\n") + "\n");
|
|
17234
|
+
drawStatus();
|
|
17235
|
+
};
|
|
17236
|
+
const take = (event) => {
|
|
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
|
+
}
|
|
17246
|
+
const key = replayKey(event);
|
|
17247
|
+
if (key) {
|
|
17248
|
+
if (replaying && seen.has(key)) {
|
|
17249
|
+
lastWasAnswer = event.type === "assistant-message";
|
|
17250
|
+
settle();
|
|
17251
|
+
return;
|
|
17252
|
+
}
|
|
17253
|
+
seen.add(key);
|
|
17254
|
+
}
|
|
17255
|
+
if (key || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
|
|
17256
|
+
if (event.type === "turn-end") sawTurnEnd = true;
|
|
17257
|
+
if (event.type === "assistant-message") sawAnswer = true;
|
|
17258
|
+
emit2(event);
|
|
17259
|
+
};
|
|
17260
|
+
return {
|
|
17261
|
+
chunk(text) {
|
|
17262
|
+
const { lines, rest } = feed(buffer, text);
|
|
17263
|
+
buffer = rest;
|
|
17264
|
+
for (const line of lines) for (const event of parseCursorLine(line)) take(event);
|
|
17265
|
+
},
|
|
17266
|
+
notice(text) {
|
|
17267
|
+
emit2({ type: "notice", text });
|
|
17268
|
+
},
|
|
17269
|
+
finish(exit, interrupted = false) {
|
|
17270
|
+
disarmStall();
|
|
17271
|
+
stopStatus();
|
|
17272
|
+
if (!sawTurnEnd) {
|
|
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: "" });
|
|
17281
|
+
}
|
|
17282
|
+
}
|
|
17283
|
+
stopStatus();
|
|
17284
|
+
return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit };
|
|
17285
|
+
}
|
|
17286
|
+
};
|
|
17287
|
+
}
|
|
16927
17288
|
async function runCursorTurn(opts) {
|
|
16928
17289
|
const write2 = opts.write || ((text) => process.stdout.write(text));
|
|
16929
17290
|
const width = opts.width || Number(process.stdout.columns || 100);
|
|
16930
|
-
|
|
16931
|
-
|
|
16932
|
-
|
|
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
|
+
});
|
|
16933
17302
|
return new Promise((resolve7) => {
|
|
16934
17303
|
const child = spawn9("cursor-agent", cursorArgs(opts.prompt), {
|
|
16935
17304
|
cwd: opts.cwd,
|
|
16936
17305
|
stdio: ["ignore", "pipe", "pipe"]
|
|
16937
17306
|
});
|
|
16938
|
-
|
|
16939
|
-
|
|
16940
|
-
const
|
|
16941
|
-
|
|
16942
|
-
|
|
16943
|
-
|
|
16944
|
-
|
|
16945
|
-
|
|
16946
|
-
|
|
16947
|
-
|
|
16948
|
-
|
|
16949
|
-
|
|
16950
|
-
|
|
16951
|
-
|
|
16952
|
-
|
|
16953
|
-
const rendered = renderEvent(event, width);
|
|
16954
|
-
if (rendered.length) write2(rendered.join("\n") + "\n");
|
|
16955
|
-
}
|
|
16956
|
-
}
|
|
16957
|
-
});
|
|
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 });
|
|
17320
|
+
child.stdout.on("data", (chunk) => sink.chunk(chunk.toString("utf8")));
|
|
17321
|
+
let lastNote = "";
|
|
16958
17322
|
child.stderr.on("data", (chunk) => {
|
|
16959
|
-
const
|
|
16960
|
-
if (
|
|
16961
|
-
|
|
17323
|
+
const note = stderrNotice(chunk.toString("utf8"));
|
|
17324
|
+
if (note && note !== lastNote) {
|
|
17325
|
+
lastNote = note;
|
|
17326
|
+
sink.notice(note);
|
|
16962
17327
|
}
|
|
16963
17328
|
});
|
|
16964
17329
|
child.on("close", (code) => {
|
|
16965
|
-
|
|
17330
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
17331
|
+
resolve7(sink.finish(code ?? 0, interrupted));
|
|
16966
17332
|
});
|
|
16967
17333
|
child.on("error", (error) => {
|
|
16968
|
-
|
|
16969
|
-
resolve7(
|
|
17334
|
+
sink.notice("failed to start cursor-agent: " + String(error));
|
|
17335
|
+
resolve7(sink.finish(1, interrupted));
|
|
16970
17336
|
});
|
|
16971
17337
|
});
|
|
16972
17338
|
}
|
|
17339
|
+
var TICK_MS;
|
|
16973
17340
|
var init_run = __esm({
|
|
16974
17341
|
"cli/harness/run.ts"() {
|
|
16975
17342
|
"use strict";
|
|
16976
17343
|
init_cursor();
|
|
16977
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();
|
|
16978
17411
|
}
|
|
16979
17412
|
});
|
|
16980
17413
|
|
|
16981
17414
|
// cli/harness/session.ts
|
|
16982
17415
|
import { createInterface as createInterface5 } from "readline";
|
|
16983
|
-
async function runOnce(harness, cwd, prompt) {
|
|
17416
|
+
async function runOnce(harness, cwd, prompt, echoPrompt = true, showHeader = true, signal) {
|
|
16984
17417
|
if (harness !== "cursor") {
|
|
16985
17418
|
process.stdout.write(S2.dim + " " + harness + " sessions are not embedded yet\n" + S2.reset);
|
|
16986
17419
|
return 1;
|
|
16987
17420
|
}
|
|
16988
|
-
const result = await runCursorTurn({ prompt, cwd });
|
|
17421
|
+
const result = await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal });
|
|
16989
17422
|
if (result.blocked > 0) {
|
|
16990
17423
|
process.stdout.write(
|
|
16991
17424
|
S2.blocked + " " + result.blocked + " action" + (result.blocked === 1 ? "" : "s") + " blocked by policy" + S2.reset + "\n"
|
|
@@ -16995,31 +17428,46 @@ async function runOnce(harness, cwd, prompt) {
|
|
|
16995
17428
|
}
|
|
16996
17429
|
async function runGovernedSession(harness, cwd, prompt) {
|
|
16997
17430
|
if (prompt) return runOnce(harness, cwd, prompt);
|
|
16998
|
-
process.stdout.write(
|
|
17431
|
+
process.stdout.write(identityHeader({ cwd, harness, identity: readIdentity(harness) }).join("\n") + "\n");
|
|
16999
17432
|
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
17000
|
-
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;
|
|
17001
17451
|
for (; ; ) {
|
|
17002
|
-
const
|
|
17452
|
+
const answer = await ask3();
|
|
17453
|
+
if (answer === null) break;
|
|
17454
|
+
const line = answer.trim();
|
|
17003
17455
|
if (!line) continue;
|
|
17004
17456
|
if (line === "exit" || line === "quit") break;
|
|
17005
|
-
|
|
17457
|
+
turn = new AbortController();
|
|
17458
|
+
await runOnce(harness, cwd, line, false, first, turn.signal);
|
|
17459
|
+
turn = null;
|
|
17460
|
+
first = false;
|
|
17006
17461
|
}
|
|
17007
17462
|
rl.close();
|
|
17008
17463
|
return 0;
|
|
17009
17464
|
}
|
|
17010
|
-
var BANNER;
|
|
17011
17465
|
var init_session = __esm({
|
|
17012
17466
|
"cli/harness/session.ts"() {
|
|
17013
17467
|
"use strict";
|
|
17014
17468
|
init_run();
|
|
17469
|
+
init_identity2();
|
|
17015
17470
|
init_render2();
|
|
17016
|
-
BANNER = [
|
|
17017
|
-
"",
|
|
17018
|
-
S2.bold + " synkro" + S2.reset + S2.dim + " governed session" + S2.reset,
|
|
17019
|
-
S2.dim + " every tool call passes Synkro policy before it runs" + S2.reset,
|
|
17020
|
-
S2.dim + " ctrl-c to leave" + S2.reset,
|
|
17021
|
-
""
|
|
17022
|
-
];
|
|
17023
17471
|
}
|
|
17024
17472
|
});
|
|
17025
17473
|
|
|
@@ -17212,7 +17660,7 @@ __export(linear_exports, {
|
|
|
17212
17660
|
linearCommand: () => linearCommand
|
|
17213
17661
|
});
|
|
17214
17662
|
import { readFileSync as readFileSync33 } from "fs";
|
|
17215
|
-
import { homedir as
|
|
17663
|
+
import { homedir as homedir39 } from "os";
|
|
17216
17664
|
import { join as join37 } from "path";
|
|
17217
17665
|
function mcpJwt() {
|
|
17218
17666
|
try {
|
|
@@ -17255,7 +17703,7 @@ var SYNKRO_DIR14, PORT2, BASE;
|
|
|
17255
17703
|
var init_linear = __esm({
|
|
17256
17704
|
"cli/commands/linear.ts"() {
|
|
17257
17705
|
"use strict";
|
|
17258
|
-
SYNKRO_DIR14 = join37(
|
|
17706
|
+
SYNKRO_DIR14 = join37(homedir39(), ".synkro");
|
|
17259
17707
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
17260
17708
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
17261
17709
|
}
|
|
@@ -17404,10 +17852,10 @@ var init_cveReachability = __esm({
|
|
|
17404
17852
|
});
|
|
17405
17853
|
|
|
17406
17854
|
// cli/reachability/reachabilityScan.ts
|
|
17407
|
-
import { spawnSync as spawnSync12, execFileSync as
|
|
17855
|
+
import { spawnSync as spawnSync12, execFileSync as execFileSync6 } from "child_process";
|
|
17408
17856
|
import { readFileSync as readFileSync35, writeFileSync as writeFileSync27, existsSync as existsSync38, readdirSync as readdirSync10 } from "fs";
|
|
17409
17857
|
import { join as join38 } from "path";
|
|
17410
|
-
import { homedir as
|
|
17858
|
+
import { homedir as homedir40 } from "os";
|
|
17411
17859
|
import { createRequire } from "module";
|
|
17412
17860
|
function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
17413
17861
|
const SKIP2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
|
|
@@ -17531,7 +17979,7 @@ function findEntries(repoRoot3) {
|
|
|
17531
17979
|
}
|
|
17532
17980
|
function currentCommit(repoRoot3) {
|
|
17533
17981
|
try {
|
|
17534
|
-
return
|
|
17982
|
+
return execFileSync6("git", ["rev-parse", "HEAD"], { cwd: repoRoot3, encoding: "utf8" }).trim();
|
|
17535
17983
|
} catch {
|
|
17536
17984
|
return "";
|
|
17537
17985
|
}
|
|
@@ -17655,7 +18103,7 @@ var init_reachabilityScan = __esm({
|
|
|
17655
18103
|
"use strict";
|
|
17656
18104
|
init_cveReachability();
|
|
17657
18105
|
require2 = createRequire(import.meta.url);
|
|
17658
|
-
REACHABILITY_PATH = join38(
|
|
18106
|
+
REACHABILITY_PATH = join38(homedir40(), ".synkro", "reachability.json");
|
|
17659
18107
|
}
|
|
17660
18108
|
});
|
|
17661
18109
|
|
|
@@ -17666,8 +18114,8 @@ __export(reachabilityScan_exports, {
|
|
|
17666
18114
|
});
|
|
17667
18115
|
import { readFileSync as readFileSync36, existsSync as existsSync39 } from "fs";
|
|
17668
18116
|
import { join as join39 } from "path";
|
|
17669
|
-
import { homedir as
|
|
17670
|
-
import { execFileSync as
|
|
18117
|
+
import { homedir as homedir41 } from "os";
|
|
18118
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
17671
18119
|
function readConfigEnv4() {
|
|
17672
18120
|
const p = join39(SYNKRO_DIR15, "config.env");
|
|
17673
18121
|
if (!existsSync39(p)) return {};
|
|
@@ -17682,7 +18130,7 @@ function readConfigEnv4() {
|
|
|
17682
18130
|
}
|
|
17683
18131
|
function repoRoot2() {
|
|
17684
18132
|
try {
|
|
17685
|
-
return
|
|
18133
|
+
return execFileSync7("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
|
|
17686
18134
|
} catch {
|
|
17687
18135
|
return process.cwd();
|
|
17688
18136
|
}
|
|
@@ -17690,7 +18138,7 @@ function repoRoot2() {
|
|
|
17690
18138
|
function repoSlug(root) {
|
|
17691
18139
|
const run2 = (a) => {
|
|
17692
18140
|
try {
|
|
17693
|
-
return
|
|
18141
|
+
return execFileSync7("git", a, { encoding: "utf-8" }).trim();
|
|
17694
18142
|
} catch {
|
|
17695
18143
|
return "";
|
|
17696
18144
|
}
|
|
@@ -17740,7 +18188,7 @@ var init_reachabilityScan2 = __esm({
|
|
|
17740
18188
|
"cli/commands/reachabilityScan.ts"() {
|
|
17741
18189
|
"use strict";
|
|
17742
18190
|
init_reachabilityScan();
|
|
17743
|
-
SYNKRO_DIR15 = join39(
|
|
18191
|
+
SYNKRO_DIR15 = join39(homedir41(), ".synkro");
|
|
17744
18192
|
}
|
|
17745
18193
|
});
|
|
17746
18194
|
|
|
@@ -17872,7 +18320,7 @@ __export(config_exports, {
|
|
|
17872
18320
|
});
|
|
17873
18321
|
import { readFileSync as readFileSync37, writeFileSync as writeFileSync28, existsSync as existsSync40 } from "fs";
|
|
17874
18322
|
import { join as join40 } from "path";
|
|
17875
|
-
import { homedir as
|
|
18323
|
+
import { homedir as homedir42 } from "os";
|
|
17876
18324
|
function readConfigEnv5() {
|
|
17877
18325
|
if (!existsSync40(CONFIG_PATH9)) return {};
|
|
17878
18326
|
const out = {};
|
|
@@ -18058,7 +18506,7 @@ var init_config = __esm({
|
|
|
18058
18506
|
"use strict";
|
|
18059
18507
|
init_stub();
|
|
18060
18508
|
init_optout();
|
|
18061
|
-
SYNKRO_DIR16 = join40(
|
|
18509
|
+
SYNKRO_DIR16 = join40(homedir42(), ".synkro");
|
|
18062
18510
|
CONFIG_PATH9 = join40(SYNKRO_DIR16, "config.env");
|
|
18063
18511
|
}
|
|
18064
18512
|
});
|
|
@@ -18250,10 +18698,10 @@ Usage:
|
|
|
18250
18698
|
// cli/inventory/identity.ts
|
|
18251
18699
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
18252
18700
|
import { existsSync as existsSync41, mkdirSync as mkdirSync24, readFileSync as readFileSync38, renameSync as renameSync9, writeFileSync as writeFileSync29 } from "fs";
|
|
18253
|
-
import { homedir as
|
|
18701
|
+
import { homedir as homedir43 } from "os";
|
|
18254
18702
|
import { dirname as dirname12, join as join41 } from "path";
|
|
18255
18703
|
function operationalIdentityPath() {
|
|
18256
|
-
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join41(
|
|
18704
|
+
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join41(homedir43(), ".synkro", "installation.json");
|
|
18257
18705
|
}
|
|
18258
18706
|
function validIdentity(value) {
|
|
18259
18707
|
if (!value || typeof value !== "object") return false;
|
|
@@ -18285,7 +18733,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
|
18285
18733
|
return identity;
|
|
18286
18734
|
}
|
|
18287
18735
|
var UUID_RE, cached4;
|
|
18288
|
-
var
|
|
18736
|
+
var init_identity3 = __esm({
|
|
18289
18737
|
"cli/inventory/identity.ts"() {
|
|
18290
18738
|
"use strict";
|
|
18291
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;
|
|
@@ -18301,7 +18749,7 @@ import {
|
|
|
18301
18749
|
readdirSync as readdirSync11,
|
|
18302
18750
|
statSync as statSync5
|
|
18303
18751
|
} from "fs";
|
|
18304
|
-
import { arch, homedir as
|
|
18752
|
+
import { arch, homedir as homedir44, hostname as hostname2, platform as platform6, release as release2 } from "os";
|
|
18305
18753
|
import { basename as basename3, join as join42, relative, resolve as resolve5 } from "path";
|
|
18306
18754
|
import { fileURLToPath } from "url";
|
|
18307
18755
|
function sha256(value) {
|
|
@@ -18312,7 +18760,7 @@ function pseudonymousHostnameHash(installationId, host) {
|
|
|
18312
18760
|
}
|
|
18313
18761
|
function cliVersion() {
|
|
18314
18762
|
try {
|
|
18315
|
-
return "1.10.
|
|
18763
|
+
return "1.10.6";
|
|
18316
18764
|
} catch {
|
|
18317
18765
|
return "0.0.0";
|
|
18318
18766
|
}
|
|
@@ -18707,7 +19155,7 @@ function harnessSnapshot(agent) {
|
|
|
18707
19155
|
};
|
|
18708
19156
|
}
|
|
18709
19157
|
function collectOperationalInventory(options = {}) {
|
|
18710
|
-
const home = options.homeDir ??
|
|
19158
|
+
const home = options.homeDir ?? homedir44();
|
|
18711
19159
|
const detected = options.detectedAgents ?? detectAgents();
|
|
18712
19160
|
const identity = getOperationalInstallationIdentity(options.identityPath);
|
|
18713
19161
|
const targetPlatform = options.platformName ?? platform6();
|
|
@@ -18819,7 +19267,7 @@ var init_collector = __esm({
|
|
|
18819
19267
|
init_ccHookConfig();
|
|
18820
19268
|
init_cursorHookConfig();
|
|
18821
19269
|
init_codexHookConfig();
|
|
18822
|
-
|
|
19270
|
+
init_identity3();
|
|
18823
19271
|
}
|
|
18824
19272
|
});
|
|
18825
19273
|
|
|
@@ -18843,10 +19291,10 @@ import {
|
|
|
18843
19291
|
renameSync as renameSync10,
|
|
18844
19292
|
writeFileSync as writeFileSync30
|
|
18845
19293
|
} from "fs";
|
|
18846
|
-
import { homedir as
|
|
19294
|
+
import { homedir as homedir45 } from "os";
|
|
18847
19295
|
import { dirname as dirname13, join as join43 } from "path";
|
|
18848
19296
|
function syncStatePath() {
|
|
18849
|
-
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join43(
|
|
19297
|
+
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join43(homedir45(), ".synkro", "inventory-sync.json");
|
|
18850
19298
|
}
|
|
18851
19299
|
function readState(path = syncStatePath()) {
|
|
18852
19300
|
try {
|
|
@@ -18873,7 +19321,7 @@ function shouldSyncInventory(state, now = Date.now(), target) {
|
|
|
18873
19321
|
return !Number.isFinite(lastAttempt) || lastAttempt <= 0 || now - lastAttempt >= FAILURE_RETRY_MS;
|
|
18874
19322
|
}
|
|
18875
19323
|
function readConfig() {
|
|
18876
|
-
const path = join43(
|
|
19324
|
+
const path = join43(homedir45(), ".synkro", "config.env");
|
|
18877
19325
|
const out = {};
|
|
18878
19326
|
try {
|
|
18879
19327
|
for (const rawLine of readFileSync40(path, "utf8").split("\n")) {
|
|
@@ -18915,7 +19363,7 @@ function resolveInventoryGateway(raw) {
|
|
|
18915
19363
|
}
|
|
18916
19364
|
async function loadToken() {
|
|
18917
19365
|
try {
|
|
18918
|
-
const durable = readFileSync40(join43(
|
|
19366
|
+
const durable = readFileSync40(join43(homedir45(), ".synkro", ".mcp-jwt"), "utf8").trim();
|
|
18919
19367
|
if (durable) return durable;
|
|
18920
19368
|
} catch {
|
|
18921
19369
|
}
|
|
@@ -19082,7 +19530,7 @@ var subArgs = args.slice(1);
|
|
|
19082
19530
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
19083
19531
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "inventory-sync", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
19084
19532
|
function printVersion() {
|
|
19085
|
-
console.log("1.10.
|
|
19533
|
+
console.log("1.10.6");
|
|
19086
19534
|
}
|
|
19087
19535
|
function printHelp2() {
|
|
19088
19536
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|