@buzzni/saycode-cli 0.3.2 → 0.3.3

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.
Files changed (2) hide show
  1. package/index.mjs +534 -65
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -40,7 +40,7 @@ function _mergeNamespaces(n, m) {
40
40
  }
41
41
  return Object.freeze(Object.defineProperty(n, Symbol.toStringTag, { value: "Module" }));
42
42
  }
43
- const version = "0.3.2";
43
+ const version = "0.3.3";
44
44
  const manifest = {
45
45
  version
46
46
  };
@@ -58,13 +58,13 @@ function parseAgentControlArgs(verb, target, rest, hasPositionalTarget) {
58
58
  return null;
59
59
  }
60
60
  const AGENT_VERBS = ["whoami", "ls", "read", "wait", "prompt", "spawn", ...AGENT_CONTROL_VERBS];
61
- function fail(message) {
61
+ function fail$1(message) {
62
62
  return { command: "error", message };
63
63
  }
64
64
  function parseAgentArgs(flags, positionals) {
65
65
  const [verb, ...rest] = positionals;
66
66
  if (!verb || !AGENT_VERBS.includes(verb)) {
67
- return fail(`agent needs one of: ${AGENT_VERBS.join(", ")}`);
67
+ return fail$1(`agent needs one of: ${AGENT_VERBS.join(", ")}`);
68
68
  }
69
69
  const timeout = flags.timeoutMs !== void 0 ? { timeoutMs: flags.timeoutMs } : {};
70
70
  const hasPositionalTarget = flags.sessionId === void 0 && rest[0] !== void 0;
@@ -77,18 +77,18 @@ function parseAgentArgs(flags, positionals) {
77
77
  case "ls":
78
78
  return { command: "agent", verb: "ls" };
79
79
  case "read": {
80
- if (!target) return fail("agent read needs a session id (see `saycode agent ls`)");
80
+ if (!target) return fail$1("agent read needs a session id (see `saycode agent ls`)");
81
81
  return { command: "agent", verb: "read", sessionId: target, ...flags.last !== void 0 ? { last: flags.last } : {} };
82
82
  }
83
83
  case "wait": {
84
- if (!target) return fail("agent wait needs a session id (see `saycode agent ls`)");
85
- if (!flags.until) return fail("agent wait needs --until turn-end|waiting-input|completed");
84
+ if (!target) return fail$1("agent wait needs a session id (see `saycode agent ls`)");
85
+ if (!flags.until) return fail$1("agent wait needs --until turn-end|waiting-input|completed");
86
86
  return { command: "agent", verb: "wait", sessionId: target, until: flags.until, ...timeout };
87
87
  }
88
88
  case "prompt": {
89
- if (!target) return fail("agent prompt needs a session id (see `saycode agent ls`)");
89
+ if (!target) return fail$1("agent prompt needs a session id (see `saycode agent ls`)");
90
90
  const text2 = rest.slice(hasPositionalTarget ? 1 : 0).join(" ").trim();
91
- if (!text2) return fail("agent prompt needs the text to send");
91
+ if (!text2) return fail$1("agent prompt needs the text to send");
92
92
  return {
93
93
  command: "agent",
94
94
  verb: "prompt",
@@ -104,16 +104,16 @@ function parseAgentArgs(flags, positionals) {
104
104
  const files = flags.files ?? [];
105
105
  if (flags.handoff) {
106
106
  if (flags.prompt !== void 0) {
107
- return fail("agent spawn --handoff sends the conversation, so it cannot also take --prompt");
107
+ return fail$1("agent spawn --handoff sends the conversation, so it cannot also take --prompt");
108
108
  }
109
109
  if (files.length > 0) {
110
- return fail("agent spawn --handoff sends the conversation, so it cannot also take --file");
110
+ return fail$1("agent spawn --handoff sends the conversation, so it cannot also take --file");
111
111
  }
112
112
  const mode = flags.mode?.trim() || "summary";
113
113
  if (mode === "full") {
114
- return fail("agent spawn --handoff does not support --mode full yet; only summary is available");
114
+ return fail$1("agent spawn --handoff does not support --mode full yet; only summary is available");
115
115
  }
116
- if (mode !== "summary") return fail(`agent spawn --handoff got an unknown --mode: ${mode}`);
116
+ if (mode !== "summary") return fail$1(`agent spawn --handoff got an unknown --mode: ${mode}`);
117
117
  const from = flags.from?.trim();
118
118
  return {
119
119
  command: "agent",
@@ -125,7 +125,7 @@ function parseAgentArgs(flags, positionals) {
125
125
  };
126
126
  }
127
127
  const text2 = flags.prompt?.trim();
128
- if (!text2) return fail("agent spawn needs --prompt <text>");
128
+ if (!text2) return fail$1("agent spawn needs --prompt <text>");
129
129
  return {
130
130
  command: "agent",
131
131
  verb: "spawn",
@@ -137,7 +137,7 @@ function parseAgentArgs(flags, positionals) {
137
137
  };
138
138
  }
139
139
  default:
140
- return fail(`unsupported agent verb: ${verb}`);
140
+ return fail$1(`unsupported agent verb: ${verb}`);
141
141
  }
142
142
  }
143
143
  function fencedMarkdownRanges(content) {
@@ -816,12 +816,21 @@ const extensionPanelKo = {
816
816
  };
817
817
  const extensionInstallKo = {
818
818
  "extensions.installOfficial": "확장 설치",
819
+ "extensions.installArtifactPublisher": "공개 링크 확장 설치",
819
820
  "extensions.installDescription": "계속하려면 공식 확장을 설치하세요.",
820
821
  "extensions.machineExecutePermission": "이 확장은 미리 정의된 동작에만 machine.execute 권한을 요청합니다.",
821
822
  "extensions.notInstalled": "요청한 확장이 설치되지 않았습니다.",
822
823
  "extensions.disabled": "요청한 확장이 비활성화되어 있습니다.",
823
824
  "extensions.quarantined": "요청한 확장이 반복된 오류로 격리되었습니다.",
824
- "extensions.unavailable": "요청한 확장이 설치되어 있지만 현재 사용할 수 없습니다."
825
+ "extensions.unavailable": "요청한 확장이 설치되어 있지만 현재 사용할 수 없습니다.",
826
+ "extensions.permission.artifactPublishPublic": "선택한 결과물의 공개 링크를 만들 수 있습니다. 파일 내용과 링크 처리는 Saycode Core가 담당합니다.",
827
+ "artifactPublication.dialogTitle": "공개 링크",
828
+ "artifactPublication.copy": "링크 복사",
829
+ "artifactPublication.copied": "복사됨",
830
+ "artifactPublication.open": "링크 열기",
831
+ "artifactPublication.revoke": "공개 중지",
832
+ "artifactPublication.revoking": "중지 중…",
833
+ "artifactPublication.failed": "공개 링크 작업에 실패했습니다."
825
834
  };
826
835
  const personalChatsLandingKo = {
827
836
  "personalChats.heroTitle": "무엇을 도와드릴까요?",
@@ -1132,7 +1141,9 @@ const machineSettingsConsoleKo = {
1132
1141
  "machineConsole.updateTimeout": "업데이트가 시작됐지만 완료를 확인하지 못했습니다. 잠시 후 다시 진단해 주세요.",
1133
1142
  "machineConsole.updateFailed": "CLI 업데이트에 실패했습니다.",
1134
1143
  "machineConsole.aiCliStarted": "Codex와 Claude Code 설치를 시작했습니다.",
1135
- "machineConsole.aiCliFailed": "AI CLI 설치를 시작하지 못했습니다."
1144
+ "machineConsole.aiCliFailed": "AI CLI 설치를 시작하지 못했습니다.",
1145
+ "machineConsole.machineActionFailed": "머신 작업에 실패했습니다.",
1146
+ "machineConsole.extensionCommandsUnavailable": "Extension 명령을 사용할 수 없습니다."
1136
1147
  };
1137
1148
  const sessionTokenUsageKo = {
1138
1149
  "sessionTokenUsage.total": "누적 {count} 토큰",
@@ -1260,7 +1271,12 @@ const additionalDirectoriesKo = {
1260
1271
  "additionalDirectories.limitReached": "추가 디렉터리는 최대 8개까지 등록할 수 있습니다.",
1261
1272
  "additionalDirectories.machineMismatch": "현재 머신과 다른 위치에서 선택한 디렉터리입니다.",
1262
1273
  "additionalDirectories.saveFailed": "디렉터리를 등록하지 못했습니다.",
1263
- "additionalDirectories.folderLoadFailed": "디렉터리를 불러오지 못했습니다."
1274
+ "additionalDirectories.folderLoadFailed": "디렉터리를 불러오지 못했습니다.",
1275
+ "additionalDirectories.relatedMountMissing": "관련 프로젝트 디렉터리 {count}개를 자동 마운트에서 제외하고 세션을 시작했습니다.",
1276
+ "additionalDirectories.relatedMountLinkLookupFailed": "관련 프로젝트 목록을 불러오지 못해 자동 마운트 없이 세션을 시작했습니다.",
1277
+ "additionalDirectories.relatedMountLookupFailed": "관련 프로젝트 {count}개를 불러오지 못해 제외하고 세션을 시작했습니다.",
1278
+ "additionalDirectories.relatedMountRuntimeUnsupported": "이 머신의 Happy CLI가 관련 프로젝트 자동 마운트를 지원하지 않아 기본 세션으로 시작했습니다.",
1279
+ "additionalDirectories.relatedMountLimit": "관련 프로젝트 디렉터리 상한을 넘어 {count}개를 제외하고 세션을 시작했습니다."
1264
1280
  };
1265
1281
  const agentHandoffKo = {
1266
1282
  "agentHandoff.open": "다른 에이전트로 이어가기",
@@ -1332,6 +1348,15 @@ const subagentKo = {
1332
1348
  const reviewNavigationKo = {
1333
1349
  "review.action.openSourceSession": "원본 대화로 이동: {title}"
1334
1350
  };
1351
+ const chatFolderUploadKo = {
1352
+ "upload.kindFolder": "폴더",
1353
+ "upload.folderReference": "{path} ({count}개 파일)",
1354
+ "upload.folderChip": "{name} · {count}개 파일",
1355
+ "upload.folderEmpty": "폴더에 업로드할 파일이 없습니다: {name}",
1356
+ "upload.progress": "파일 업로드 중 {done}/{total}",
1357
+ "upload.partialIssue": "업로드 실패 {failed}개 · 건너뜀 {skipped}개",
1358
+ "upload.invalidPath": "안전하지 않은 첨부 경로입니다."
1359
+ };
1335
1360
  const aiCommitPrKo = {
1336
1361
  "aiCommitPr.stagedCount": "스테이지된 파일 {count}개",
1337
1362
  "aiCommitPr.noStaged": "스테이지된 변경이 없습니다.",
@@ -1404,7 +1429,14 @@ const projectLinksKo = {
1404
1429
  "projectLinks.mcpToggleHint": "켜면 AI가 관련 프로젝트로 작업을 제안했을 때 확인 없이 바로 진행됩니다.",
1405
1430
  "projectLinks.mcpToggleFailed": "설정을 바꾸지 못했습니다."
1406
1431
  };
1407
- const catalogExtensionsKo = { ...taskNotificationKo, ...machineUsageKo, ...completionInboxKo, ...agentBoardKo, ...fanoutLifecycleKo, ...conversationSearchKo, ...browserCookieImportKo, ...orcaPortKo, ...usageAnalyticsKo, ...outboundWebhookKo, ...scheduledAutomationsKo, ...githubTriggersKo, ...memoryHubKo, ...extensionPanelKo, ...extensionInstallKo, ...personalChatsKo, ...newSessionStarterKo, ...browserConsoleCaptureKo, ...completionSignalKo, ...agentLoopDetectionKo, ...operationModelRetryKo, ...skillCatalogKo, ...ciAutoFixKo, ...collaborationKo, ...backgroundTaskKo, ...sessionTitleKo, ...accountSettingsKo, ...projectScopeKo, ...machineSettingsConsoleKo, ...sessionTokenUsageKo, ...worktreeSetupKo, ...connectionIndicatorKo, ...voiceDictationKo, ...newSessionProfileKo, ...agentBoardBroadcastKo, ...judgementEvidenceKo, ...releaseNoteKo, ...notificationSoundKo, ...officeViewerKo, ...additionalDirectoriesKo, ...agentHandoffKo, ...sidebarUpdateKo, ...autopilotSelfReviewRoundsKo, ...aiCommitPrKo, ...externalWorktreeInboxKo, ...externalSessionVaultKo, ...terminalFontKo, ...globalPauseKo, ...subagentKo, ...reviewNavigationKo, ...projectLinksKo, ...projectHandoffKo };
1432
+ const connectorReadinessKo = {
1433
+ "connectorReadiness.badge": "커넥터 {count}건 확인 필요",
1434
+ "connectorReadiness.state.needsAuth": "다시 연결 필요",
1435
+ "connectorReadiness.state.runtimeFailed": "연결 실패",
1436
+ "connectorReadiness.state.configMissing": "설정 없음",
1437
+ "connectorReadiness.state.unknown": "상태 미확인"
1438
+ };
1439
+ const catalogExtensionsKo = { ...taskNotificationKo, ...machineUsageKo, ...completionInboxKo, ...agentBoardKo, ...fanoutLifecycleKo, ...conversationSearchKo, ...browserCookieImportKo, ...orcaPortKo, ...usageAnalyticsKo, ...outboundWebhookKo, ...scheduledAutomationsKo, ...githubTriggersKo, ...memoryHubKo, ...extensionPanelKo, ...extensionInstallKo, ...personalChatsKo, ...newSessionStarterKo, ...browserConsoleCaptureKo, ...completionSignalKo, ...agentLoopDetectionKo, ...operationModelRetryKo, ...skillCatalogKo, ...ciAutoFixKo, ...collaborationKo, ...backgroundTaskKo, ...sessionTitleKo, ...accountSettingsKo, ...projectScopeKo, ...machineSettingsConsoleKo, ...sessionTokenUsageKo, ...worktreeSetupKo, ...connectionIndicatorKo, ...voiceDictationKo, ...newSessionProfileKo, ...agentBoardBroadcastKo, ...judgementEvidenceKo, ...releaseNoteKo, ...notificationSoundKo, ...officeViewerKo, ...additionalDirectoriesKo, ...agentHandoffKo, ...sidebarUpdateKo, ...autopilotSelfReviewRoundsKo, ...aiCommitPrKo, ...externalWorktreeInboxKo, ...externalSessionVaultKo, ...terminalFontKo, ...globalPauseKo, ...subagentKo, ...reviewNavigationKo, ...projectLinksKo, ...projectHandoffKo, ...chatFolderUploadKo, ...connectorReadinessKo };
1408
1440
  const onboardingKo = {
1409
1441
  "onboarding.dialogAria": "시작 설정",
1410
1442
  "onboarding.eyebrow": "처음 시작하기",
@@ -1500,6 +1532,44 @@ const ko = {
1500
1532
  "common.reset": "초기화",
1501
1533
  "common.cancel": "취소",
1502
1534
  "common.close": "닫기",
1535
+ "networkDataSaver.transferConfirm": "{operation} 작업은 데이터를 많이 사용할 수 있습니다. 예상 크기: {size}. 계속할까요?",
1536
+ "networkDataSaver.sizeUnknown": "알 수 없음",
1537
+ "networkDataSaver.operation.update-download": "업데이트 다운로드",
1538
+ "networkDataSaver.operation.file-upload": "파일 업로드",
1539
+ "networkDataSaver.operation.folder-upload": "폴더 업로드",
1540
+ "networkDataSaver.operation.file-download": "파일 다운로드",
1541
+ "networkDataSaver.operation.project-deploy": "프로젝트 배포",
1542
+ "networkDataSaver.operation.project-import": "프로젝트 가져오기",
1543
+ "networkDataSaver.title": "네트워크 데이터 절약",
1544
+ "networkDataSaver.description": "macOS가 비용이 들거나 저데이터 모드인 경로로 판단하면 백그라운드 데이터 사용을 자동으로 줄입니다.",
1545
+ "networkDataSaver.loading": "네트워크 상태를 확인하는 중…",
1546
+ "networkDataSaver.unsupported": "이 앱 버전에서는 네트워크 비용 상태를 확인할 수 없습니다.",
1547
+ "networkDataSaver.status.active": "데이터 절약 중",
1548
+ "networkDataSaver.status.inactive": "데이터 절약 꺼짐",
1549
+ "networkDataSaver.modeLabel": "절약 방식",
1550
+ "networkDataSaver.mode.automatic": "자동",
1551
+ "networkDataSaver.mode.always-save": "항상 절약",
1552
+ "networkDataSaver.mode.never-save": "절약 안 함",
1553
+ "networkDataSaver.interfaceLabel": "현재 연결",
1554
+ "networkDataSaver.interface.wifi": "Wi-Fi",
1555
+ "networkDataSaver.interface.wired-ethernet": "유선 이더넷",
1556
+ "networkDataSaver.interface.cellular": "셀룰러",
1557
+ "networkDataSaver.interface.loopback": "로컬 연결",
1558
+ "networkDataSaver.interface.other": "기타 연결",
1559
+ "networkDataSaver.interface.unknown": "알 수 없음",
1560
+ "networkDataSaver.reasonLabel": "판단 이유",
1561
+ "networkDataSaver.reason.expensive": "비용이 드는 네트워크로 감지됨",
1562
+ "networkDataSaver.reason.constrained": "저데이터 모드로 감지됨",
1563
+ "networkDataSaver.reason.offline": "오프라인",
1564
+ "networkDataSaver.reason.unrestricted": "일반 네트워크로 감지됨",
1565
+ "networkDataSaver.reason.unknown": "네트워크 비용 상태를 알 수 없음",
1566
+ "networkDataSaver.reason.user-always": "사용자가 항상 절약을 선택함",
1567
+ "networkDataSaver.reason.user-never": "사용자가 절약 안 함을 선택함",
1568
+ "networkDataSaver.deferred.title": "절약 중 지연되는 작업",
1569
+ "networkDataSaver.activeHelp": "수동 새로고침은 계속 사용할 수 있고, 대용량 작업은 확인 후 한 번 허용할 수 있습니다.",
1570
+ "networkDataSaver.deferred.model": "난이도 모델 최초 다운로드",
1571
+ "networkDataSaver.deferred.memory": "Memory Layer 대화 백필",
1572
+ "networkDataSaver.deferred.gitPr": "백그라운드 Git 및 PR 새로고침",
1503
1573
  "happyCliUpdate.title": "Happy CLI 업데이트가 있습니다",
1504
1574
  "happyCliUpdate.checkTitle": "Happy CLI 업데이트 확인",
1505
1575
  "happyCliUpdate.managerTitle": "Happy CLI 업데이트 관리",
@@ -3397,6 +3467,7 @@ const ko = {
3397
3467
  "file.readFailed": "파일을 읽지 못했습니다.",
3398
3468
  "session.loadMessagesFailed": "대화 메시지를 불러올 수 없습니다.",
3399
3469
  "session.sendToSessionFailed": "대화에 메시지를 보낼 수 없습니다.",
3470
+ "session.sendBlockedAccountKeyLocked": "이 대화는 계정 키로 잠겨 있습니다. 잠금을 해제한 뒤 다시 시도하세요.",
3400
3471
  "message.noEncryptKey": "메시지 삭제 이벤트를 암호화할 세션 키를 찾을 수 없습니다.",
3401
3472
  "machineSel.boundMissing": "프로젝트에 연결된 머신 정보를 찾을 수 없습니다. 새로고침 후 다시 시도해주세요.",
3402
3473
  "machineSel.connectedMachine": "연결된 머신",
@@ -3784,6 +3855,7 @@ const ko = {
3784
3855
  "settingsTab.language": "언어",
3785
3856
  "settingsTab.machines": "머신",
3786
3857
  "settingsTab.mobile": "모바일 연결",
3858
+ "settingsTab.network": "네트워크 데이터 절약",
3787
3859
  "settingsTab.env": "환경 변수",
3788
3860
  "settingsTab.security": "보안",
3789
3861
  "settingsTab.memory": "Memory",
@@ -3792,12 +3864,15 @@ const ko = {
3792
3864
  "settingsTab.extensions": "확장",
3793
3865
  "settingsTab.about": "정보",
3794
3866
  "settingsTab.shortcuts": "단축키",
3795
- "extensions.help": "필요한 기능만 로컬 확장 패키지로 설치하고 관리합니다.",
3867
+ "extensions.help": "필요한 기능만 공식 또는 로컬 확장 패키지로 설치하고 관리합니다.",
3796
3868
  "extensions.installLocal": "로컬 파일 설치",
3797
3869
  "extensions.loading": "설치된 확장을 불러오는 중입니다.",
3798
3870
  "extensions.empty": "설치된 확장이 없습니다.",
3799
3871
  "extensions.permissions": "권한",
3800
3872
  "extensions.noPermissions": "요청 권한 없음",
3873
+ "extensions.permission.browserViewerOpen": "접근 가능한 온라인 머신에서 원격 Chrome 화면을 준비해 기본 브라우저로 엽니다. 소프트웨어 설치는 포함하지 않습니다.",
3874
+ "extensions.permission.browserViewerInstall": "선택한 머신에 원격 화면용 시스템 패키지(Xvfb/noVNC)를 설치합니다. Chrome 설치는 포함하지 않습니다.",
3875
+ "extensions.permission.browserViewerInstallChrome": "선택한 머신에 Google Chrome을 설치합니다. 머신의 시스템 소프트웨어가 변경됩니다.",
3801
3876
  "extensions.approvePermission": "'{permission}' 승인",
3802
3877
  "extensions.revokePermission": "'{permission}' 철회",
3803
3878
  "extensions.enable": "활성화",
@@ -6904,6 +6979,147 @@ function decryptJsonWithCandidates(blob, keys) {
6904
6979
  decryptCacheSet(blob, keys, null);
6905
6980
  return null;
6906
6981
  }
6982
+ function bounded(value2, maxLength) {
6983
+ if (typeof value2 !== "string" || value2.length === 0 || value2.length > maxLength) return void 0;
6984
+ return value2;
6985
+ }
6986
+ function parseCloudLoginResult(raw) {
6987
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
6988
+ throw new Error("CLOUD_LOGIN_RESPONSE_INVALID");
6989
+ }
6990
+ const value2 = raw;
6991
+ const required = (field, maxLength) => {
6992
+ const entry = bounded(value2[field], maxLength);
6993
+ if (entry === void 0) throw new Error("CLOUD_LOGIN_RESPONSE_INVALID");
6994
+ return entry;
6995
+ };
6996
+ const accountPublicKey = bounded(value2.accountPublicKey, 128);
6997
+ const accountKeySalt = bounded(value2.accountKeySalt, 128);
6998
+ return {
6999
+ userId: required("userId", 256),
7000
+ username: required("username", 256),
7001
+ token: required("token", 8192),
7002
+ secret: required("secret", 8192),
7003
+ ...accountPublicKey ? { accountPublicKey } : {},
7004
+ ...accountKeySalt ? { accountKeySalt } : {}
7005
+ };
7006
+ }
7007
+ const ENVELOPE_VERSION = 0;
7008
+ const EPHEMERAL_PUBLIC_KEY_LENGTH = 32;
7009
+ const ENVELOPE_NONCE_LENGTH = 24;
7010
+ const ENVELOPE_HEADER_LENGTH = 1 + EPHEMERAL_PUBLIC_KEY_LENGTH + ENVELOPE_NONCE_LENGTH;
7011
+ const GCM_VERSION = 0;
7012
+ const GCM_NONCE_LENGTH = 12;
7013
+ const GCM_TAG_LENGTH = 16;
7014
+ function unwrapDataEncryptionKey(envelopeBase64, recipientPrivateKey) {
7015
+ let envelope;
7016
+ try {
7017
+ envelope = decodeBase64(envelopeBase64);
7018
+ } catch {
7019
+ return null;
7020
+ }
7021
+ if (envelope.length <= ENVELOPE_HEADER_LENGTH) return null;
7022
+ if (envelope[0] !== ENVELOPE_VERSION) return null;
7023
+ const ephemeralPublicKey = envelope.subarray(1, 1 + EPHEMERAL_PUBLIC_KEY_LENGTH);
7024
+ const nonce = envelope.subarray(1 + EPHEMERAL_PUBLIC_KEY_LENGTH, ENVELOPE_HEADER_LENGTH);
7025
+ const ciphertext = envelope.subarray(ENVELOPE_HEADER_LENGTH);
7026
+ const opened = nacl.box.open(ciphertext, nonce, ephemeralPublicKey, recipientPrivateKey);
7027
+ return opened ? new Uint8Array(opened) : null;
7028
+ }
7029
+ async function importGcmKey(dataEncryptionKey) {
7030
+ return crypto.subtle.importKey("raw", new Uint8Array(dataEncryptionKey), "AES-GCM", false, ["encrypt", "decrypt"]);
7031
+ }
7032
+ async function decryptDataKeyToString(dataEncryptionKey, bundleBase64) {
7033
+ let bundle;
7034
+ try {
7035
+ bundle = decodeBase64(bundleBase64);
7036
+ } catch {
7037
+ return null;
7038
+ }
7039
+ if (bundle.length < 1 + GCM_NONCE_LENGTH + GCM_TAG_LENGTH) return null;
7040
+ if (bundle[0] !== GCM_VERSION) return null;
7041
+ const nonce = bundle.subarray(1, 1 + GCM_NONCE_LENGTH);
7042
+ const ciphertextWithTag = bundle.subarray(1 + GCM_NONCE_LENGTH);
7043
+ try {
7044
+ const key = await importGcmKey(dataEncryptionKey);
7045
+ const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: new Uint8Array(nonce) }, key, new Uint8Array(ciphertextWithTag));
7046
+ return new TextDecoder().decode(plaintext);
7047
+ } catch {
7048
+ return null;
7049
+ }
7050
+ }
7051
+ async function encryptDataKeyContent(dataEncryptionKey, value2) {
7052
+ const nonce = nacl.randomBytes(GCM_NONCE_LENGTH);
7053
+ const key = await importGcmKey(dataEncryptionKey);
7054
+ const plaintext = new TextEncoder().encode(JSON.stringify(value2));
7055
+ const ciphertextWithTag = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: new Uint8Array(nonce) }, key, plaintext));
7056
+ const bundle = new Uint8Array(1 + GCM_NONCE_LENGTH + ciphertextWithTag.length);
7057
+ bundle[0] = GCM_VERSION;
7058
+ bundle.set(nonce, 1);
7059
+ bundle.set(ciphertextWithTag, 1 + GCM_NONCE_LENGTH);
7060
+ return encodeBase64(bundle);
7061
+ }
7062
+ const dekBySessionId = /* @__PURE__ */ new Map();
7063
+ const lockedSessionIds = /* @__PURE__ */ new Set();
7064
+ const fingerprints = /* @__PURE__ */ new Map();
7065
+ function registerSessionDek(session, accountPrivateKey) {
7066
+ const envelope = session.dataEncryptionKey ?? null;
7067
+ if (!envelope) {
7068
+ dekBySessionId.delete(session.id);
7069
+ lockedSessionIds.delete(session.id);
7070
+ fingerprints.delete(session.id);
7071
+ return;
7072
+ }
7073
+ const fingerprint = `${envelope}|${accountPrivateKey ? "1" : "0"}`;
7074
+ if (fingerprints.get(session.id) === fingerprint) return;
7075
+ fingerprints.set(session.id, fingerprint);
7076
+ const dek = accountPrivateKey ? unwrapDataEncryptionKey(envelope, accountPrivateKey) : null;
7077
+ if (dek) {
7078
+ dekBySessionId.set(session.id, dek);
7079
+ lockedSessionIds.delete(session.id);
7080
+ } else {
7081
+ dekBySessionId.delete(session.id);
7082
+ lockedSessionIds.add(session.id);
7083
+ }
7084
+ }
7085
+ function getSessionDek(sessionId) {
7086
+ return dekBySessionId.get(sessionId) ?? null;
7087
+ }
7088
+ const MAX_ENTRIES = 3e3;
7089
+ const headerCache = /* @__PURE__ */ new Map();
7090
+ function cacheGet(store, sessionId, ciphertext) {
7091
+ return store.get(sessionId)?.get(ciphertext) ?? null;
7092
+ }
7093
+ function cacheSet(store, sessionId, ciphertext, plaintext) {
7094
+ let perSession = store.get(sessionId);
7095
+ if (!perSession) {
7096
+ perSession = /* @__PURE__ */ new Map();
7097
+ store.set(sessionId, perSession);
7098
+ }
7099
+ perSession.set(ciphertext, plaintext);
7100
+ if (perSession.size > MAX_ENTRIES) {
7101
+ const oldest = perSession.keys().next().value;
7102
+ if (oldest !== void 0) perSession.delete(oldest);
7103
+ }
7104
+ }
7105
+ function getCachedDataKeyMetadataJson(sessionId, ciphertext) {
7106
+ return cacheGet(headerCache, sessionId, ciphertext);
7107
+ }
7108
+ async function preDecryptSessionHeaders(sessions, accountPrivateKey) {
7109
+ await Promise.all(sessions.map(async (session) => {
7110
+ registerSessionDek(session, accountPrivateKey);
7111
+ const dek = getSessionDek(session.id);
7112
+ if (!dek) return;
7113
+ if (session.metadata && getCachedDataKeyMetadataJson(session.id, session.metadata) === null) {
7114
+ const json = await decryptDataKeyToString(dek, session.metadata);
7115
+ if (json !== null) cacheSet(headerCache, session.id, session.metadata, json);
7116
+ }
7117
+ if (session.agentState && getCachedDataKeyMetadataJson(session.id, session.agentState) === null) {
7118
+ const json = await decryptDataKeyToString(dek, session.agentState);
7119
+ if (json !== null) cacheSet(headerCache, session.id, session.agentState, json);
7120
+ }
7121
+ }));
7122
+ }
6907
7123
  function buildUserTextMessageContent(input) {
6908
7124
  return {
6909
7125
  role: "user",
@@ -7022,7 +7238,7 @@ function personalMachinesPath(companyId) {
7022
7238
  function myMachinesPath(companyId) {
7023
7239
  return `/api/admin/companies/${encodeURIComponent(companyId)}/my-machines`;
7024
7240
  }
7025
- function record$1(value2) {
7241
+ function record$2(value2) {
7026
7242
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
7027
7243
  }
7028
7244
  function array(value2) {
@@ -7041,7 +7257,7 @@ function provider(value2) {
7041
7257
  return PROVIDERS.includes(str(value2)) ? str(value2) : null;
7042
7258
  }
7043
7259
  function parseBundle(value2) {
7044
- const row2 = record$1(value2);
7260
+ const row2 = record$2(value2);
7045
7261
  const id = str(row2.id);
7046
7262
  const bundleProvider = provider(row2.provider);
7047
7263
  if (!id || !bundleProvider) return null;
@@ -7060,7 +7276,7 @@ function parseBundle(value2) {
7060
7276
  };
7061
7277
  }
7062
7278
  function parseDeployment(value2) {
7063
- const row2 = record$1(value2);
7279
+ const row2 = record$2(value2);
7064
7280
  const machineId = str(row2.machineId);
7065
7281
  const bundleId = str(row2.bundleId);
7066
7282
  if (!machineId || !bundleId) return null;
@@ -7080,10 +7296,10 @@ function parseDeployment(value2) {
7080
7296
  };
7081
7297
  }
7082
7298
  function parseStatus(value2) {
7083
- const row2 = record$1(value2);
7299
+ const row2 = record$2(value2);
7084
7300
  const statusProvider = provider(row2.provider);
7085
7301
  if (!statusProvider) return void 0;
7086
- const rotationRow = record$1(row2.rotation);
7302
+ const rotationRow = record$2(row2.rotation);
7087
7303
  const state = str(rotationRow.state);
7088
7304
  const rotation = ROTATION_STATES.includes(state) ? {
7089
7305
  state,
@@ -7104,7 +7320,7 @@ function parseStatus(value2) {
7104
7320
  };
7105
7321
  }
7106
7322
  function parseMachineResult(value2) {
7107
- const row2 = record$1(value2);
7323
+ const row2 = record$2(value2);
7108
7324
  const machineId = str(row2.machineId);
7109
7325
  if (!machineId) return null;
7110
7326
  if (row2.ok !== true) {
@@ -7119,28 +7335,28 @@ function parseMachineResult(value2) {
7119
7335
  };
7120
7336
  }
7121
7337
  function parseAiCredentialVaultSnapshot(value2) {
7122
- const body = record$1(value2);
7338
+ const body = record$2(value2);
7123
7339
  return {
7124
7340
  bundles: array(body.bundles).map(parseBundle).filter((bundle) => bundle !== null),
7125
7341
  deployments: array(body.deployments).map(parseDeployment).filter((deployment) => deployment !== null)
7126
7342
  };
7127
7343
  }
7128
7344
  function parseAiCredentialCaptureResponse(value2) {
7129
- const body = record$1(value2);
7345
+ const body = record$2(value2);
7130
7346
  const bundle = parseBundle(body.bundle);
7131
7347
  if (!bundle) throw new Error("AI credential capture response did not contain a bundle");
7132
7348
  return { bundle, warnings: array(body.warnings).filter((warning) => typeof warning === "string") };
7133
7349
  }
7134
7350
  function parseAiCredentialBatchResult(value2) {
7135
- const body = record$1(value2);
7351
+ const body = record$2(value2);
7136
7352
  return {
7137
7353
  results: array(body.results).map(parseMachineResult).filter((result) => result !== null),
7138
7354
  warnings: array(body.warnings).filter((warning) => typeof warning === "string")
7139
7355
  };
7140
7356
  }
7141
7357
  function parseSelfServiceMachines(value2) {
7142
- return array(record$1(value2).machines).map((entry) => {
7143
- const row2 = record$1(entry);
7358
+ return array(record$2(value2).machines).map((entry) => {
7359
+ const row2 = record$2(entry);
7144
7360
  return {
7145
7361
  machineId: str(row2.machineId),
7146
7362
  type: row2.type === "shared" ? "shared" : "personal",
@@ -7153,8 +7369,8 @@ function parseSelfServiceMachines(value2) {
7153
7369
  }).filter((machine) => machine.machineId !== "");
7154
7370
  }
7155
7371
  function parsePersonalMachines(value2) {
7156
- return array(record$1(value2).machines).map((entry) => {
7157
- const row2 = record$1(entry);
7372
+ return array(record$2(value2).machines).map((entry) => {
7373
+ const row2 = record$2(entry);
7158
7374
  const type = row2.type === "shared" ? "shared" : "personal";
7159
7375
  return {
7160
7376
  machineId: str(row2.machineId),
@@ -7524,6 +7740,123 @@ function parseProjectListV2Page(body) {
7524
7740
  total: page.total
7525
7741
  };
7526
7742
  }
7743
+ const PUBLIC_ID_PATTERN = /^[A-Za-z0-9_-]{32}$/;
7744
+ const CHECKSUM_PATTERN = /^[a-f\d]{64}$/i;
7745
+ const MAX_ID_LENGTH = 256;
7746
+ const MAX_TITLE_LENGTH = 255;
7747
+ const MAX_WARNINGS = 32;
7748
+ const MAX_WARNING_LENGTH = 500;
7749
+ const PUBLICATION_KEYS = /* @__PURE__ */ new Set([
7750
+ "id",
7751
+ "publicId",
7752
+ "sourceType",
7753
+ "sourceIdentity",
7754
+ "sourceChecksumSha256",
7755
+ "title",
7756
+ "contentType",
7757
+ "sizeBytes",
7758
+ "checksumSha256",
7759
+ "status",
7760
+ "createdAt",
7761
+ "revokedAt",
7762
+ "publicUrl"
7763
+ ]);
7764
+ function fail(detail) {
7765
+ throw new Error(`artifact publication response is invalid: ${detail}`);
7766
+ }
7767
+ function record$1(value2, label) {
7768
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) fail(`${label} must be an object`);
7769
+ return value2;
7770
+ }
7771
+ function exactKeys(value2, keys) {
7772
+ const expected = new Set(keys);
7773
+ if (Object.keys(value2).some((key) => !expected.has(key)) || keys.some((key) => !(key in value2))) fail("response fields do not match the contract");
7774
+ }
7775
+ function nonEmptyString$1(value2, label, maxLength = MAX_ID_LENGTH) {
7776
+ if (typeof value2 !== "string" || !value2 || value2.trim() !== value2 || value2.length > maxLength) fail(`${label} is invalid`);
7777
+ return value2;
7778
+ }
7779
+ function checksum(value2, label) {
7780
+ if (typeof value2 !== "string" || !CHECKSUM_PATTERN.test(value2)) fail(`${label} is invalid`);
7781
+ }
7782
+ function safeInteger(value2, label) {
7783
+ if (typeof value2 !== "number" || !Number.isSafeInteger(value2) || value2 < 0) fail(`${label} is invalid`);
7784
+ }
7785
+ function warnings(value2) {
7786
+ if (!Array.isArray(value2) || value2.length > MAX_WARNINGS || value2.some((warning) => typeof warning !== "string" || warning.length > MAX_WARNING_LENGTH)) fail("warnings are invalid");
7787
+ return [...value2];
7788
+ }
7789
+ function isLoopbackHostname(hostname) {
7790
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
7791
+ }
7792
+ function parseTrustedPublicUrl(value2, apiBaseUrl, publicId) {
7793
+ if (typeof value2 !== "string") fail("publicUrl is invalid");
7794
+ let publicUrl;
7795
+ let apiUrl;
7796
+ try {
7797
+ publicUrl = new URL(value2);
7798
+ apiUrl = new URL(apiBaseUrl);
7799
+ } catch {
7800
+ fail("publicUrl is invalid");
7801
+ }
7802
+ if (publicUrl.protocol !== "https:" && !(publicUrl.protocol === "http:" && isLoopbackHostname(publicUrl.hostname)) || publicUrl.origin !== apiUrl.origin || publicUrl.protocol !== apiUrl.protocol || publicUrl.username || publicUrl.password || publicUrl.search || publicUrl.hash || publicUrl.pathname !== `/artifacts/${publicId}`) fail("publicUrl is not trusted");
7803
+ return publicUrl.toString();
7804
+ }
7805
+ function sourceIdentity(source) {
7806
+ return source.type === "project-file" ? JSON.stringify([
7807
+ source.projectId,
7808
+ ...source.worktreeId ? [source.worktreeId] : [],
7809
+ source.path
7810
+ ]) : JSON.stringify([source.chatId, source.path]);
7811
+ }
7812
+ function validatePublication(value2, apiBaseUrl, operation, expectedSource) {
7813
+ const publication = record$1(value2, "publication");
7814
+ if (Object.keys(publication).some((key) => !PUBLICATION_KEYS.has(key))) {
7815
+ fail("publication contains an unexpected field");
7816
+ }
7817
+ if ([...PUBLICATION_KEYS].some((key) => !(key in publication))) {
7818
+ fail("publication is missing a field");
7819
+ }
7820
+ const publicationId = nonEmptyString$1(publication.id, "publication id");
7821
+ const publicId = nonEmptyString$1(publication.publicId, "public id");
7822
+ if (!PUBLIC_ID_PATTERN.test(publicId)) fail("public id is invalid");
7823
+ if (!["project-file", "personal-chat-file", "personal-chat-artifact"].includes(String(publication.sourceType))) {
7824
+ fail("source type is invalid");
7825
+ }
7826
+ const parsedSourceIdentity = nonEmptyString$1(publication.sourceIdentity, "source identity", 4096);
7827
+ if (expectedSource && (publication.sourceType !== expectedSource.type || parsedSourceIdentity !== sourceIdentity(expectedSource))) fail("source does not match the request");
7828
+ checksum(publication.sourceChecksumSha256, "source checksum");
7829
+ const title = nonEmptyString$1(publication.title, "title", MAX_TITLE_LENGTH);
7830
+ nonEmptyString$1(publication.contentType, "content type");
7831
+ safeInteger(publication.sizeBytes, "size");
7832
+ checksum(publication.checksumSha256, "checksum");
7833
+ safeInteger(publication.createdAt, "created time");
7834
+ const expectedStatus = operation === "publish" ? "active" : "revoked";
7835
+ if (publication.status !== expectedStatus) fail("status does not match the operation");
7836
+ if (publication.revokedAt !== null && typeof publication.revokedAt !== "number" || typeof publication.revokedAt === "number" && (!Number.isSafeInteger(publication.revokedAt) || publication.revokedAt < 0) || expectedStatus === "active" && publication.revokedAt !== null || expectedStatus === "revoked" && publication.revokedAt === null) fail("revoked time is invalid");
7837
+ return {
7838
+ publicationId,
7839
+ publicId,
7840
+ publicUrl: parseTrustedPublicUrl(publication.publicUrl, apiBaseUrl, publicId),
7841
+ title,
7842
+ status: expectedStatus
7843
+ };
7844
+ }
7845
+ function parseStaticArtifactPublicationResult(value2, options) {
7846
+ const response = record$1(value2, "response");
7847
+ if (options.operation === "publish") {
7848
+ exactKeys(response, ["publication", "publicUrl", "reused"]);
7849
+ if (typeof response.reused !== "boolean") fail("reused is invalid");
7850
+ const publication2 = validatePublication(response.publication, options.apiBaseUrl, options.operation, options.source);
7851
+ const publicUrl = parseTrustedPublicUrl(response.publicUrl, options.apiBaseUrl, publication2.publicId);
7852
+ if (publication2.publicUrl !== publicUrl) fail("public URLs do not match");
7853
+ return { version: 1, ...publication2, reused: response.reused, warnings: [] };
7854
+ }
7855
+ exactKeys(response, ["publication", "warnings"]);
7856
+ const parsedWarnings = warnings(response.warnings);
7857
+ const publication = validatePublication(response.publication, options.apiBaseUrl, options.operation);
7858
+ return { version: 1, ...publication, reused: false, warnings: parsedWarnings };
7859
+ }
7527
7860
  function asRecord(value2) {
7528
7861
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7529
7862
  }
@@ -16132,6 +16465,18 @@ function coerceCreatedBy(value2) {
16132
16465
  const displayName = nonEmptyString(value2.displayName);
16133
16466
  return displayName ? { accountId, displayName } : { accountId };
16134
16467
  }
16468
+ function decryptSessionJsonSync(sessionId, blob, candidateKeys) {
16469
+ if (!blob) return null;
16470
+ const cached = getCachedDataKeyMetadataJson(sessionId, blob);
16471
+ if (cached !== null) {
16472
+ try {
16473
+ return JSON.parse(cached);
16474
+ } catch {
16475
+ return null;
16476
+ }
16477
+ }
16478
+ return decryptJsonWithCandidates(blob, candidateKeys);
16479
+ }
16135
16480
  function apiSessionToSession(api, candidateKeys, existing) {
16136
16481
  const updatedAt = mergeUpdatedAt(api.updatedAt);
16137
16482
  const summaryText = decryptSummaryText(api.metadata, candidateKeys) ?? existing?.summaryText;
@@ -16141,8 +16486,8 @@ function apiSessionToSession(api, candidateKeys, existing) {
16141
16486
  const pendingExitPlanMode = apiSessionAgentStatePendingExitPlanMode(api.agentState, candidateKeys);
16142
16487
  const pendingPermissionRequests = apiSessionAgentStatePendingPermissions(api.agentState, candidateKeys);
16143
16488
  const agentControlledByUser = apiSessionAgentStateControlledByUser(api.agentState, candidateKeys);
16144
- const agentState = api.agentState ? decryptJsonWithCandidates(api.agentState, candidateKeys) : null;
16145
- const metadata = decryptJsonWithCandidates(api.metadata, candidateKeys);
16489
+ const agentState = api.agentState ? decryptSessionJsonSync(api.id, api.agentState, candidateKeys) : null;
16490
+ const metadata = decryptSessionJsonSync(api.id, api.metadata, candidateKeys);
16146
16491
  const agent = agentFromFlavor(typeof metadata?.flavor === "string" ? metadata.flavor : void 0) ?? existing?.agent;
16147
16492
  const models = coerceSessionModels(metadata?.models) ?? existing?.models;
16148
16493
  const worktree = coerceSessionWorktree(metadata?.worktree) ?? coerceSessionWorktree(metadata) ?? existing?.worktree;
@@ -16486,18 +16831,18 @@ function readCloudMobileLoginPairingErrorCode(body) {
16486
16831
  const PAIRING_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
16487
16832
  const CODE_PATTERN = /^[A-Za-z0-9_-]{43}$/;
16488
16833
  const STATUSES = /* @__PURE__ */ new Set(["pending", "consumed", "revoked", "expired"]);
16489
- function invalidResponse() {
16834
+ function invalidResponse$1() {
16490
16835
  throw new Error("PAIRING_RESPONSE_INVALID");
16491
16836
  }
16492
16837
  function parseCloudMobileLoginPairing(raw, options = {}) {
16493
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) invalidResponse();
16838
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) invalidResponse$1();
16494
16839
  const value2 = raw;
16495
- if (typeof value2.pairingId !== "string" || !PAIRING_ID_PATTERN.test(value2.pairingId)) invalidResponse();
16496
- if (!Number.isSafeInteger(value2.expiresAt) || value2.expiresAt <= 0) invalidResponse();
16497
- if (!Number.isSafeInteger(value2.serverTime) || value2.serverTime <= 0) invalidResponse();
16498
- if (typeof value2.status !== "string" || !STATUSES.has(value2.status)) invalidResponse();
16499
- if (options.requireCode && (typeof value2.code !== "string" || !CODE_PATTERN.test(value2.code))) invalidResponse();
16500
- if (value2.code !== void 0 && (typeof value2.code !== "string" || !CODE_PATTERN.test(value2.code))) invalidResponse();
16840
+ if (typeof value2.pairingId !== "string" || !PAIRING_ID_PATTERN.test(value2.pairingId)) invalidResponse$1();
16841
+ if (!Number.isSafeInteger(value2.expiresAt) || value2.expiresAt <= 0) invalidResponse$1();
16842
+ if (!Number.isSafeInteger(value2.serverTime) || value2.serverTime <= 0) invalidResponse$1();
16843
+ if (typeof value2.status !== "string" || !STATUSES.has(value2.status)) invalidResponse$1();
16844
+ if (options.requireCode && (typeof value2.code !== "string" || !CODE_PATTERN.test(value2.code))) invalidResponse$1();
16845
+ if (value2.code !== void 0 && (typeof value2.code !== "string" || !CODE_PATTERN.test(value2.code))) invalidResponse$1();
16501
16846
  return {
16502
16847
  pairingId: value2.pairingId,
16503
16848
  expiresAt: value2.expiresAt,
@@ -16506,6 +16851,43 @@ function parseCloudMobileLoginPairing(raw, options = {}) {
16506
16851
  ...typeof value2.code === "string" ? { code: value2.code } : {}
16507
16852
  };
16508
16853
  }
16854
+ const BROWSER_VIEWER_OPEN_TIMEOUT_MS = 13e4;
16855
+ const BROWSER_VIEWER_INSTALL_TIMEOUT_MS = 31e4;
16856
+ function invalidResponse() {
16857
+ throw new Error("BROWSER_VIEWER_RESPONSE_INVALID");
16858
+ }
16859
+ function responseRecord(value2) {
16860
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) invalidResponse();
16861
+ return value2;
16862
+ }
16863
+ function parseBrowserViewerOpenResponse(value2) {
16864
+ const response = responseRecord(value2);
16865
+ if (typeof response.token !== "string" || response.token.length === 0 || typeof response.relayUrl !== "string" && response.relayUrl !== null || response.routingVersion !== 1 || typeof response.previewDomain !== "string" && response.previewDomain !== null || !Number.isInteger(response.webPort) || response.webPort < 1 || response.webPort > 65535 || typeof response.bridgeReady !== "boolean") invalidResponse();
16866
+ return {
16867
+ token: response.token,
16868
+ relayUrl: response.relayUrl,
16869
+ routingVersion: 1,
16870
+ previewDomain: response.previewDomain,
16871
+ webPort: response.webPort,
16872
+ bridgeReady: response.bridgeReady
16873
+ };
16874
+ }
16875
+ function parseBrowserViewerInstallResponse(value2) {
16876
+ const response = responseRecord(value2);
16877
+ if (response.action === "already-installed") return { kind: "already-installed" };
16878
+ if (response.action === "run") {
16879
+ if (response.ok !== true) invalidResponse();
16880
+ return { kind: "installed" };
16881
+ }
16882
+ if (response.action === "manual") {
16883
+ if (typeof response.command !== "string" || typeof response.reason !== "string") invalidResponse();
16884
+ return {
16885
+ kind: "manual-required",
16886
+ reason: response.command.length === 0 ? "unsupported-platform" : "admin-required"
16887
+ };
16888
+ }
16889
+ return invalidResponse();
16890
+ }
16509
16891
  const FIXED_API_BASE_URL = "https://saycode.ai";
16510
16892
  const FIXED_SYNC_BASE_URL = "https://saycode.ai";
16511
16893
  const machineRegistryCache = /* @__PURE__ */ new Map();
@@ -16686,6 +17068,7 @@ function normalizeMachine(row2, candidateKeys) {
16686
17068
  mcpCallerGrantPublicKey: readMcpCallerGrantPublicKey(daemonState),
16687
17069
  automationSupport,
16688
17070
  filesystemScope: decrypted.filesystemScope,
17071
+ additionalDirectories: decrypted.additionalDirectories,
16689
17072
  registry
16690
17073
  };
16691
17074
  }
@@ -16713,7 +17096,7 @@ class ApiClient {
16713
17096
  headers: { "Content-Type": "application/json" },
16714
17097
  body: JSON.stringify(input)
16715
17098
  }, DEFAULT_REQUEST_TIMEOUT_MS);
16716
- return this.handleResponse(response);
17099
+ return parseCloudLoginResult(await this.handleResponse(response));
16717
17100
  }
16718
17101
  async getMe() {
16719
17102
  return this.request("/api/auth/me", { boundary: "api" });
@@ -17952,23 +18335,26 @@ class ApiClient {
17952
18335
  boundary: "api"
17953
18336
  });
17954
18337
  }
17955
- async fetchSessions(candidateKeys = [], tokenOverride) {
18338
+ async fetchSessions(candidateKeys = [], tokenOverride, accountPrivateKey = null) {
17956
18339
  const data = await this.request("/v1/sessions", {
17957
18340
  boundary: "sync",
17958
18341
  tokenOverride
17959
18342
  });
17960
18343
  const rows = Array.isArray(data) ? data : data.sessions ?? [];
18344
+ await preDecryptSessionHeaders(rows, accountPrivateKey);
17961
18345
  return rows.map((row2) => apiSessionToSession(row2, candidateKeys));
17962
18346
  }
17963
- async fetchSessionsPage(candidateKeys = [], cursor, limit = 200, tokenOverride) {
18347
+ async fetchSessionsPage(candidateKeys = [], cursor, limit = 200, tokenOverride, accountPrivateKey = null) {
17964
18348
  const params = new URLSearchParams({ limit: String(limit) });
17965
18349
  if (cursor) params.set("cursor", cursor);
17966
18350
  const data = await this.request(
17967
18351
  `/v2/sessions?${params.toString()}`,
17968
18352
  { boundary: "sync", tokenOverride }
17969
18353
  );
18354
+ const rows = data.sessions ?? [];
18355
+ await preDecryptSessionHeaders(rows, accountPrivateKey);
17970
18356
  return {
17971
- sessions: (data.sessions ?? []).map((row2) => apiSessionToSession(row2, candidateKeys)),
18357
+ sessions: rows.map((row2) => apiSessionToSession(row2, candidateKeys)),
17972
18358
  nextCursor: data.nextCursor ?? null,
17973
18359
  hasNext: data.hasNext === true
17974
18360
  };
@@ -17983,8 +18369,10 @@ class ApiClient {
17983
18369
  });
17984
18370
  return data.sessions ?? [];
17985
18371
  }
17986
- async lookupSessions(candidateKeys, ids, tokenOverride) {
17987
- return (await this.lookupApiSessions(ids, tokenOverride)).map((row2) => apiSessionToSession(row2, candidateKeys));
18372
+ async lookupSessions(candidateKeys, ids, tokenOverride, accountPrivateKey = null) {
18373
+ const rows = await this.lookupApiSessions(ids, tokenOverride);
18374
+ await preDecryptSessionHeaders(rows, accountPrivateKey);
18375
+ return rows.map((row2) => apiSessionToSession(row2, candidateKeys));
17988
18376
  }
17989
18377
  async deleteSession(sessionId, tokenOverride) {
17990
18378
  await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}`, {
@@ -18169,9 +18557,8 @@ class ApiClient {
18169
18557
  }
18170
18558
  async sendUserTextMessage(input) {
18171
18559
  const localId = input.localId || `desktop-${Date.now()}-${Math.random().toString(36).slice(2)}`;
18172
- const key = input.secretBase64 ? decodeBase64(input.secretBase64) : null;
18173
18560
  const content = buildUserTextMessageContent(input);
18174
- const encoded = encodeMessageContent(content, key);
18561
+ const encoded = input.dataEncryptionKey ? await encryptDataKeyContent(input.dataEncryptionKey, content) : encodeMessageContent(content, input.secretBase64 ? decodeBase64(input.secretBase64) : null);
18175
18562
  const result = await this.sendMessages(input.sessionId, [{ localId, content: encoded }], input.tokenOverride);
18176
18563
  return { localId, sentSeq: result.messages?.[0]?.seq };
18177
18564
  }
@@ -18243,6 +18630,34 @@ class ApiClient {
18243
18630
  }
18244
18631
  return resolvedProjectPreviewRelayUrl({ ...input, response: { ...response, token: response.token }, syncBaseUrl: this.config.syncBaseUrl });
18245
18632
  }
18633
+ async openBrowserViewer(machineId, signal) {
18634
+ return parseBrowserViewerOpenResponse(await this.request(
18635
+ `/api/machines/${encodeURIComponent(machineId)}/browser-viewer/open`,
18636
+ {
18637
+ method: "POST",
18638
+ boundary: "api",
18639
+ signal,
18640
+ timeoutMs: BROWSER_VIEWER_OPEN_TIMEOUT_MS
18641
+ }
18642
+ ));
18643
+ }
18644
+ async installBrowserViewer(machineId, signal) {
18645
+ return this.installBrowserViewerComponent(machineId, "install", signal);
18646
+ }
18647
+ async installBrowserViewerChrome(machineId, signal) {
18648
+ return this.installBrowserViewerComponent(machineId, "install-chrome", signal);
18649
+ }
18650
+ async installBrowserViewerComponent(machineId, action, signal) {
18651
+ return parseBrowserViewerInstallResponse(await this.request(
18652
+ `/api/machines/${encodeURIComponent(machineId)}/browser-viewer/${action}`,
18653
+ {
18654
+ method: "POST",
18655
+ boundary: "api",
18656
+ signal,
18657
+ timeoutMs: BROWSER_VIEWER_INSTALL_TIMEOUT_MS
18658
+ }
18659
+ ));
18660
+ }
18246
18661
  // --- Project deploy & share (specs/desktop-project-deploy-share) ---
18247
18662
  async moveProject(projectId, input) {
18248
18663
  return this.request(`/api/projects/${encodeURIComponent(projectId)}/move`, {
@@ -18364,6 +18779,30 @@ class ApiClient {
18364
18779
  timeoutMs: LONG_REQUEST_TIMEOUT_MS
18365
18780
  });
18366
18781
  }
18782
+ async publishStaticArtifact(source, signal) {
18783
+ const { version: _version, ...serverSource } = source;
18784
+ const response = await this.request("/api/artifact-publications", {
18785
+ method: "POST",
18786
+ boundary: "api",
18787
+ body: JSON.stringify({ source: serverSource }),
18788
+ signal
18789
+ });
18790
+ return parseStaticArtifactPublicationResult(response, {
18791
+ apiBaseUrl: this.config.apiBaseUrl,
18792
+ operation: "publish",
18793
+ source
18794
+ });
18795
+ }
18796
+ async revokeStaticArtifactPublication(publicationId, signal) {
18797
+ const response = await this.request(
18798
+ `/api/artifact-publications/${encodeURIComponent(publicationId)}`,
18799
+ { method: "DELETE", boundary: "api", signal }
18800
+ );
18801
+ return parseStaticArtifactPublicationResult(response, {
18802
+ apiBaseUrl: this.config.apiBaseUrl,
18803
+ operation: "revoke"
18804
+ });
18805
+ }
18367
18806
  async getDeployGitHubFiles(projectId, branch = "main") {
18368
18807
  const data = await this.request("/deploy/github-files", {
18369
18808
  method: "POST",
@@ -19784,11 +20223,11 @@ function reduceSessionReadState(state, change, merge, fingerprintLimit = DEFAULT
19784
20223
  const { item } = change;
19785
20224
  if (state.fingerprints.includes(item.fingerprint)) return state;
19786
20225
  if (change.kind === "event" && item.cursor !== null && state.cursor !== null && item.cursor < state.cursor) return state;
19787
- const fingerprints = [...state.fingerprints, item.fingerprint].slice(-Math.max(1, fingerprintLimit));
20226
+ const fingerprints2 = [...state.fingerprints, item.fingerprint].slice(-Math.max(1, fingerprintLimit));
19788
20227
  return {
19789
20228
  value: merge(state.value, item.value),
19790
20229
  cursor: item.cursor === null ? state.cursor : Math.max(state.cursor ?? item.cursor, item.cursor),
19791
- fingerprints
20230
+ fingerprints: fingerprints2
19792
20231
  };
19793
20232
  }
19794
20233
  function record(value2) {
@@ -20741,7 +21180,7 @@ function chatUploadRelPath(file, id) {
20741
21180
  if (file.type.startsWith("image/")) return `.aplus/uploads/${id}.${extensionFor(file)}`;
20742
21181
  return `.aplus/uploads/${id}-${safeFileName(file)}`;
20743
21182
  }
20744
- async function uploadChatFileToMachineCore(file, workspaceRoot, deps) {
21183
+ async function uploadChatFileToMachineCore(file, workspaceRoot, deps, relativePath) {
20745
21184
  const root = workspaceRoot.trim().replace(/\/+$/, "");
20746
21185
  if (!root) throw new Error(translateCurrent("upload.noWorkspaceRoot"));
20747
21186
  const id = (deps.now?.() ?? Date.now()).toString(36) + (deps.random?.() ?? Math.random()).toString(36).slice(2, 8);
@@ -21190,6 +21629,7 @@ const USAGE_SETTINGS_NAVIGATION_DESTINATIONS = [
21190
21629
  "settings_shortcuts",
21191
21630
  "settings_machines",
21192
21631
  "settings_mobile",
21632
+ "settings_network",
21193
21633
  "settings_env",
21194
21634
  "settings_security",
21195
21635
  "settings_memory",
@@ -24043,13 +24483,16 @@ function parseSpawnRecord(value2) {
24043
24483
  }
24044
24484
  const LEDGER_FILE_MODE = 384;
24045
24485
  const PREVIEW_MAX_LENGTH = 200;
24046
- function agentLineagePath(homeDir) {
24486
+ function agentLineagePath(rootDir) {
24487
+ return join(rootDir, ".aplus", "agent-lineage.jsonl");
24488
+ }
24489
+ function legacyAgentLineagePath(homeDir) {
24047
24490
  return join(homeDir, ".saycode", "agent-lineage.jsonl");
24048
24491
  }
24049
- function readSpawnRecords(homeDir) {
24492
+ function readLedger(path) {
24050
24493
  let raw;
24051
24494
  try {
24052
- raw = readFileSync(agentLineagePath(homeDir), "utf8");
24495
+ raw = readFileSync(path, "utf8");
24053
24496
  } catch (error) {
24054
24497
  if (error.code === "ENOENT") return [];
24055
24498
  throw error;
@@ -24067,12 +24510,38 @@ function readSpawnRecords(homeDir) {
24067
24510
  }
24068
24511
  return records;
24069
24512
  }
24070
- function countSpawnsFor(homeDir, agentKey) {
24071
- return readSpawnRecords(homeDir).filter((record2) => record2.agentKey === agentKey).length;
24513
+ function recordIdentity(record2) {
24514
+ return JSON.stringify([
24515
+ record2.ts,
24516
+ record2.agentKey,
24517
+ record2.root,
24518
+ record2.depth,
24519
+ record2.childSessionId,
24520
+ record2.parentSessionId ?? null,
24521
+ record2.agent,
24522
+ record2.promptPreview,
24523
+ record2.outcome
24524
+ ]);
24525
+ }
24526
+ function readSpawnRecords(homeDir, rootDir) {
24527
+ const records = [
24528
+ ...readLedger(legacyAgentLineagePath(homeDir)),
24529
+ ...readLedger(agentLineagePath(rootDir))
24530
+ ];
24531
+ const seen = /* @__PURE__ */ new Set();
24532
+ return records.filter((record2) => {
24533
+ const identity = recordIdentity(record2);
24534
+ if (seen.has(identity)) return false;
24535
+ seen.add(identity);
24536
+ return true;
24537
+ });
24538
+ }
24539
+ function countSpawnsFor(homeDir, rootDir, agentKey) {
24540
+ return readSpawnRecords(homeDir, rootDir).filter((record2) => record2.agentKey === agentKey).length;
24072
24541
  }
24073
- function appendSpawnRecord(homeDir, record2) {
24074
- const path = agentLineagePath(homeDir);
24075
- mkdirSync(join(homeDir, ".saycode"), { recursive: true, mode: 448 });
24542
+ function appendSpawnRecord(rootDir, record2) {
24543
+ const path = agentLineagePath(rootDir);
24544
+ mkdirSync(join(rootDir, ".aplus"), { recursive: true, mode: 448 });
24076
24545
  const entry = { ...record2, promptPreview: record2.promptPreview.slice(0, PREVIEW_MAX_LENGTH) };
24077
24546
  appendFileSync(path, `${JSON.stringify(entry)}
24078
24547
  `, { mode: LEDGER_FILE_MODE });
@@ -24276,7 +24745,7 @@ async function openStream(context, transport, sessionId) {
24276
24745
  }
24277
24746
  function createAgentRuntime(context, transport, env = process.env) {
24278
24747
  const keys = [decodeBase64(context.syncCredential.secret)];
24279
- const homeDir = context.homeDir;
24748
+ const homeDir = context.homeDir, lineageRoot = readAgentEnv(env)?.root ?? context.homeDir;
24280
24749
  const credentialHome = context.credential.source === "saycode" ? null : dirname(context.credential.path);
24281
24750
  return {
24282
24751
  env,
@@ -24317,9 +24786,9 @@ function createAgentRuntime(context, transport, env = process.env) {
24317
24786
  return { sessionId: result.sessionId };
24318
24787
  },
24319
24788
  newAgentId: () => randomBytes(8).toString("hex"),
24320
- countSpawns: (agentKey) => countSpawnsFor(homeDir, agentKey),
24321
- recordSpawn: (record2) => appendSpawnRecord(homeDir, record2),
24322
- readLineage: () => readSpawnRecords(homeDir),
24789
+ countSpawns: (agentKey) => countSpawnsFor(homeDir, lineageRoot, agentKey),
24790
+ recordSpawn: (record2) => appendSpawnRecord(lineageRoot, record2),
24791
+ readLineage: () => readSpawnRecords(homeDir, lineageRoot),
24323
24792
  steerSession: (sessionId, text2) => steerAgentSession(transport, sessionId, text2),
24324
24793
  stopSession: (machineId, sessionId) => stopAgentSession(transport, machineId, sessionId),
24325
24794
  // Omitted entirely without API access: a Happy `access.key` is sync-only, so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buzzni/saycode-cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Drive Saycode sessions across your machines from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {