yzcode-cli 1.0.1 → 1.0.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 (117) hide show
  1. package/assistant/sessionHistory.ts +87 -0
  2. package/bootstrap/state.ts +1769 -0
  3. package/bridge/bridgeApi.ts +539 -0
  4. package/bridge/bridgeConfig.ts +48 -0
  5. package/bridge/bridgeDebug.ts +135 -0
  6. package/bridge/bridgeEnabled.ts +202 -0
  7. package/bridge/bridgeMain.ts +2999 -0
  8. package/bridge/bridgeMessaging.ts +461 -0
  9. package/bridge/bridgePermissionCallbacks.ts +43 -0
  10. package/bridge/bridgePointer.ts +210 -0
  11. package/bridge/bridgeStatusUtil.ts +163 -0
  12. package/bridge/bridgeUI.ts +530 -0
  13. package/bridge/capacityWake.ts +56 -0
  14. package/bridge/codeSessionApi.ts +168 -0
  15. package/bridge/createSession.ts +384 -0
  16. package/bridge/debugUtils.ts +141 -0
  17. package/bridge/envLessBridgeConfig.ts +165 -0
  18. package/bridge/flushGate.ts +71 -0
  19. package/bridge/inboundAttachments.ts +175 -0
  20. package/bridge/inboundMessages.ts +80 -0
  21. package/bridge/initReplBridge.ts +569 -0
  22. package/bridge/jwtUtils.ts +256 -0
  23. package/bridge/pollConfig.ts +110 -0
  24. package/bridge/pollConfigDefaults.ts +82 -0
  25. package/bridge/remoteBridgeCore.ts +1008 -0
  26. package/bridge/replBridge.ts +2406 -0
  27. package/bridge/replBridgeHandle.ts +36 -0
  28. package/bridge/replBridgeTransport.ts +370 -0
  29. package/bridge/sessionIdCompat.ts +57 -0
  30. package/bridge/sessionRunner.ts +550 -0
  31. package/bridge/trustedDevice.ts +210 -0
  32. package/bridge/types.ts +262 -0
  33. package/bridge/workSecret.ts +127 -0
  34. package/buddy/CompanionSprite.tsx +371 -0
  35. package/buddy/companion.ts +133 -0
  36. package/buddy/prompt.ts +36 -0
  37. package/buddy/sprites.ts +514 -0
  38. package/buddy/types.ts +148 -0
  39. package/buddy/useBuddyNotification.tsx +98 -0
  40. package/coordinator/coordinatorMode.ts +369 -0
  41. package/memdir/findRelevantMemories.ts +141 -0
  42. package/memdir/memdir.ts +507 -0
  43. package/memdir/memoryAge.ts +53 -0
  44. package/memdir/memoryScan.ts +94 -0
  45. package/memdir/memoryTypes.ts +271 -0
  46. package/memdir/paths.ts +278 -0
  47. package/memdir/teamMemPaths.ts +292 -0
  48. package/memdir/teamMemPrompts.ts +100 -0
  49. package/migrations/migrateAutoUpdatesToSettings.ts +61 -0
  50. package/migrations/migrateBypassPermissionsAcceptedToSettings.ts +40 -0
  51. package/migrations/migrateEnableAllProjectMcpServersToSettings.ts +118 -0
  52. package/migrations/migrateFennecToOpus.ts +45 -0
  53. package/migrations/migrateLegacyOpusToCurrent.ts +57 -0
  54. package/migrations/migrateOpusToOpus1m.ts +43 -0
  55. package/migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.ts +22 -0
  56. package/migrations/migrateSonnet1mToSonnet45.ts +48 -0
  57. package/migrations/migrateSonnet45ToSonnet46.ts +67 -0
  58. package/migrations/resetAutoModeOptInForDefaultOffer.ts +51 -0
  59. package/migrations/resetProToOpusDefault.ts +51 -0
  60. package/native-ts/color-diff/index.ts +999 -0
  61. package/native-ts/file-index/index.ts +370 -0
  62. package/native-ts/yoga-layout/enums.ts +134 -0
  63. package/native-ts/yoga-layout/index.ts +2578 -0
  64. package/outputStyles/loadOutputStylesDir.ts +98 -0
  65. package/package.json +22 -5
  66. package/plugins/builtinPlugins.ts +159 -0
  67. package/plugins/bundled/index.ts +23 -0
  68. package/schemas/hooks.ts +222 -0
  69. package/screens/Doctor.tsx +575 -0
  70. package/screens/REPL.tsx +5006 -0
  71. package/screens/ResumeConversation.tsx +399 -0
  72. package/server/createDirectConnectSession.ts +88 -0
  73. package/server/directConnectManager.ts +213 -0
  74. package/server/types.ts +57 -0
  75. package/skills/bundled/batch.ts +124 -0
  76. package/skills/bundled/claudeApi.ts +196 -0
  77. package/skills/bundled/claudeApiContent.ts +75 -0
  78. package/skills/bundled/claudeInChrome.ts +34 -0
  79. package/skills/bundled/debug.ts +103 -0
  80. package/skills/bundled/index.ts +79 -0
  81. package/skills/bundled/keybindings.ts +339 -0
  82. package/skills/bundled/loop.ts +92 -0
  83. package/skills/bundled/loremIpsum.ts +282 -0
  84. package/skills/bundled/remember.ts +82 -0
  85. package/skills/bundled/scheduleRemoteAgents.ts +447 -0
  86. package/skills/bundled/simplify.ts +69 -0
  87. package/skills/bundled/skillify.ts +197 -0
  88. package/skills/bundled/stuck.ts +79 -0
  89. package/skills/bundled/updateConfig.ts +475 -0
  90. package/skills/bundled/verify/SKILL.md +3 -0
  91. package/skills/bundled/verify/examples/cli.md +3 -0
  92. package/skills/bundled/verify/examples/server.md +3 -0
  93. package/skills/bundled/verify.ts +30 -0
  94. package/skills/bundled/verifyContent.ts +13 -0
  95. package/skills/bundledSkills.ts +220 -0
  96. package/skills/loadSkillsDir.ts +1086 -0
  97. package/skills/mcpSkillBuilders.ts +44 -0
  98. package/tasks/DreamTask/DreamTask.ts +157 -0
  99. package/tasks/InProcessTeammateTask/InProcessTeammateTask.tsx +126 -0
  100. package/tasks/InProcessTeammateTask/types.ts +121 -0
  101. package/tasks/LocalAgentTask/LocalAgentTask.tsx +683 -0
  102. package/tasks/LocalMainSessionTask.ts +479 -0
  103. package/tasks/LocalShellTask/LocalShellTask.tsx +523 -0
  104. package/tasks/LocalShellTask/guards.ts +41 -0
  105. package/tasks/LocalShellTask/killShellTasks.ts +76 -0
  106. package/tasks/RemoteAgentTask/RemoteAgentTask.tsx +856 -0
  107. package/tasks/pillLabel.ts +82 -0
  108. package/tasks/stopTask.ts +100 -0
  109. package/tasks/types.ts +46 -0
  110. package/upstreamproxy/relay.ts +455 -0
  111. package/upstreamproxy/upstreamproxy.ts +285 -0
  112. package/vim/motions.ts +82 -0
  113. package/vim/operators.ts +556 -0
  114. package/vim/textObjects.ts +186 -0
  115. package/vim/transitions.ts +490 -0
  116. package/vim/types.ts +199 -0
  117. package/voice/voiceModeEnabled.ts +54 -0
@@ -0,0 +1,210 @@
1
+ import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises'
2
+ import { dirname, join } from 'path'
3
+ import { z } from 'zod/v4'
4
+ import { logForDebugging } from '../utils/debug.js'
5
+ import { isENOENT } from '../utils/errors.js'
6
+ import { getWorktreePathsPortable } from '../utils/getWorktreePathsPortable.js'
7
+ import { lazySchema } from '../utils/lazySchema.js'
8
+ import {
9
+ getProjectsDir,
10
+ sanitizePath,
11
+ } from '../utils/sessionStoragePortable.js'
12
+ import { jsonParse, jsonStringify } from '../utils/slowOperations.js'
13
+
14
+ /**
15
+ * Upper bound on worktree fanout. git worktree list is naturally bounded
16
+ * (50 is a LOT), but this caps the parallel stat() burst and guards against
17
+ * pathological setups. Above this, --continue falls back to current-dir-only.
18
+ */
19
+ const MAX_WORKTREE_FANOUT = 50
20
+
21
+ /**
22
+ * Crash-recovery pointer for Remote Control sessions.
23
+ *
24
+ * Written immediately after a bridge session is created, periodically
25
+ * refreshed during the session, and cleared on clean shutdown. If the
26
+ * process dies unclean (crash, kill -9, terminal closed), the pointer
27
+ * persists. On next startup, `claude remote-control` detects it and offers
28
+ * to resume via the --session-id flow from #20460.
29
+ *
30
+ * Staleness is checked against the file's mtime (not an embedded timestamp)
31
+ * so that a periodic re-write with the same content serves as a refresh —
32
+ * matches the backend's rolling BRIDGE_LAST_POLL_TTL (4h) semantics. A
33
+ * bridge that's been polling for 5+ hours and then crashes still has a
34
+ * fresh pointer as long as the refresh ran within the window.
35
+ *
36
+ * Scoped per working directory (alongside transcript JSONL files) so two
37
+ * concurrent bridges in different repos don't clobber each other.
38
+ */
39
+
40
+ export const BRIDGE_POINTER_TTL_MS = 4 * 60 * 60 * 1000
41
+
42
+ const BridgePointerSchema = lazySchema(() =>
43
+ z.object({
44
+ sessionId: z.string(),
45
+ environmentId: z.string(),
46
+ source: z.enum(['standalone', 'repl']),
47
+ }),
48
+ )
49
+
50
+ export type BridgePointer = z.infer<ReturnType<typeof BridgePointerSchema>>
51
+
52
+ export function getBridgePointerPath(dir: string): string {
53
+ return join(getProjectsDir(), sanitizePath(dir), 'bridge-pointer.json')
54
+ }
55
+
56
+ /**
57
+ * Write the pointer. Also used to refresh mtime during long sessions —
58
+ * calling with the same IDs is a cheap no-content-change write that bumps
59
+ * the staleness clock. Best-effort — a crash-recovery file must never
60
+ * itself cause a crash. Logs and swallows on error.
61
+ */
62
+ export async function writeBridgePointer(
63
+ dir: string,
64
+ pointer: BridgePointer,
65
+ ): Promise<void> {
66
+ const path = getBridgePointerPath(dir)
67
+ try {
68
+ await mkdir(dirname(path), { recursive: true })
69
+ await writeFile(path, jsonStringify(pointer), 'utf8')
70
+ logForDebugging(`[bridge:pointer] wrote ${path}`)
71
+ } catch (err: unknown) {
72
+ logForDebugging(`[bridge:pointer] write failed: ${err}`, { level: 'warn' })
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Read the pointer and its age (ms since last write). Operates directly
78
+ * and handles errors — no existence check (CLAUDE.md TOCTOU rule). Returns
79
+ * null on any failure: missing file, corrupted JSON, schema mismatch, or
80
+ * stale (mtime > 4h ago). Stale/invalid pointers are deleted so they don't
81
+ * keep re-prompting after the backend has already GC'd the env.
82
+ */
83
+ export async function readBridgePointer(
84
+ dir: string,
85
+ ): Promise<(BridgePointer & { ageMs: number }) | null> {
86
+ const path = getBridgePointerPath(dir)
87
+ let raw: string
88
+ let mtimeMs: number
89
+ try {
90
+ // stat for mtime (staleness anchor), then read. Two syscalls, but both
91
+ // are needed — mtime IS the data we return, not a TOCTOU guard.
92
+ mtimeMs = (await stat(path)).mtimeMs
93
+ raw = await readFile(path, 'utf8')
94
+ } catch {
95
+ return null
96
+ }
97
+
98
+ const parsed = BridgePointerSchema().safeParse(safeJsonParse(raw))
99
+ if (!parsed.success) {
100
+ logForDebugging(`[bridge:pointer] invalid schema, clearing: ${path}`)
101
+ await clearBridgePointer(dir)
102
+ return null
103
+ }
104
+
105
+ const ageMs = Math.max(0, Date.now() - mtimeMs)
106
+ if (ageMs > BRIDGE_POINTER_TTL_MS) {
107
+ logForDebugging(`[bridge:pointer] stale (>4h mtime), clearing: ${path}`)
108
+ await clearBridgePointer(dir)
109
+ return null
110
+ }
111
+
112
+ return { ...parsed.data, ageMs }
113
+ }
114
+
115
+ /**
116
+ * Worktree-aware read for `--continue`. The REPL bridge writes its pointer
117
+ * to `getOriginalCwd()` which EnterWorktreeTool/activeWorktreeSession can
118
+ * mutate to a worktree path — but `claude remote-control --continue` runs
119
+ * with `resolve('.')` = shell CWD. This fans out across git worktree
120
+ * siblings to find the freshest pointer, matching /resume's semantics.
121
+ *
122
+ * Fast path: checks `dir` first. Only shells out to `git worktree list` if
123
+ * that misses — the common case (pointer in launch dir) is one stat, zero
124
+ * exec. Fanout reads run in parallel; capped at MAX_WORKTREE_FANOUT.
125
+ *
126
+ * Returns the pointer AND the dir it was found in, so the caller can clear
127
+ * the right file on resume failure.
128
+ */
129
+ export async function readBridgePointerAcrossWorktrees(
130
+ dir: string,
131
+ ): Promise<{ pointer: BridgePointer & { ageMs: number }; dir: string } | null> {
132
+ // Fast path: current dir. Covers standalone bridge (always matches) and
133
+ // REPL bridge when no worktree mutation happened.
134
+ const here = await readBridgePointer(dir)
135
+ if (here) {
136
+ return { pointer: here, dir }
137
+ }
138
+
139
+ // Fanout: scan worktree siblings. getWorktreePathsPortable has a 5s
140
+ // timeout and returns [] on any error (not a git repo, git not installed).
141
+ const worktrees = await getWorktreePathsPortable(dir)
142
+ if (worktrees.length <= 1) return null
143
+ if (worktrees.length > MAX_WORKTREE_FANOUT) {
144
+ logForDebugging(
145
+ `[bridge:pointer] ${worktrees.length} worktrees exceeds fanout cap ${MAX_WORKTREE_FANOUT}, skipping`,
146
+ )
147
+ return null
148
+ }
149
+
150
+ // Dedupe against `dir` so we don't re-stat it. sanitizePath normalizes
151
+ // case/separators so worktree-list output matches our fast-path key even
152
+ // on Windows where git may emit C:/ vs stored c:/.
153
+ const dirKey = sanitizePath(dir)
154
+ const candidates = worktrees.filter(wt => sanitizePath(wt) !== dirKey)
155
+
156
+ // Parallel stat+read. Each readBridgePointer is a stat() that ENOENTs
157
+ // for worktrees with no pointer (cheap) plus a ~100-byte read for the
158
+ // rare ones that have one. Promise.all → latency ≈ slowest single stat.
159
+ const results = await Promise.all(
160
+ candidates.map(async wt => {
161
+ const p = await readBridgePointer(wt)
162
+ return p ? { pointer: p, dir: wt } : null
163
+ }),
164
+ )
165
+
166
+ // Pick freshest (lowest ageMs). The pointer stores environmentId so
167
+ // resume reconnects to the right env regardless of which worktree
168
+ // --continue was invoked from.
169
+ let freshest: {
170
+ pointer: BridgePointer & { ageMs: number }
171
+ dir: string
172
+ } | null = null
173
+ for (const r of results) {
174
+ if (r && (!freshest || r.pointer.ageMs < freshest.pointer.ageMs)) {
175
+ freshest = r
176
+ }
177
+ }
178
+ if (freshest) {
179
+ logForDebugging(
180
+ `[bridge:pointer] fanout found pointer in worktree ${freshest.dir} (ageMs=${freshest.pointer.ageMs})`,
181
+ )
182
+ }
183
+ return freshest
184
+ }
185
+
186
+ /**
187
+ * Delete the pointer. Idempotent — ENOENT is expected when the process
188
+ * shut down clean previously.
189
+ */
190
+ export async function clearBridgePointer(dir: string): Promise<void> {
191
+ const path = getBridgePointerPath(dir)
192
+ try {
193
+ await unlink(path)
194
+ logForDebugging(`[bridge:pointer] cleared ${path}`)
195
+ } catch (err: unknown) {
196
+ if (!isENOENT(err)) {
197
+ logForDebugging(`[bridge:pointer] clear failed: ${err}`, {
198
+ level: 'warn',
199
+ })
200
+ }
201
+ }
202
+ }
203
+
204
+ function safeJsonParse(raw: string): unknown {
205
+ try {
206
+ return jsonParse(raw)
207
+ } catch {
208
+ return null
209
+ }
210
+ }
@@ -0,0 +1,163 @@
1
+ import {
2
+ getClaudeAiBaseUrl,
3
+ getRemoteSessionUrl,
4
+ } from '../constants/product.js'
5
+ import { stringWidth } from '../ink/stringWidth.js'
6
+ import { formatDuration, truncateToWidth } from '../utils/format.js'
7
+ import { getGraphemeSegmenter } from '../utils/intl.js'
8
+
9
+ /** Bridge status state machine states. */
10
+ export type StatusState =
11
+ | 'idle'
12
+ | 'attached'
13
+ | 'titled'
14
+ | 'reconnecting'
15
+ | 'failed'
16
+
17
+ /** How long a tool activity line stays visible after last tool_start (ms). */
18
+ export const TOOL_DISPLAY_EXPIRY_MS = 30_000
19
+
20
+ /** Interval for the shimmer animation tick (ms). */
21
+ export const SHIMMER_INTERVAL_MS = 150
22
+
23
+ export function timestamp(): string {
24
+ const now = new Date()
25
+ const h = String(now.getHours()).padStart(2, '0')
26
+ const m = String(now.getMinutes()).padStart(2, '0')
27
+ const s = String(now.getSeconds()).padStart(2, '0')
28
+ return `${h}:${m}:${s}`
29
+ }
30
+
31
+ export { formatDuration, truncateToWidth as truncatePrompt }
32
+
33
+ /** Abbreviate a tool activity summary for the trail display. */
34
+ export function abbreviateActivity(summary: string): string {
35
+ return truncateToWidth(summary, 30)
36
+ }
37
+
38
+ /** Build the connect URL shown when the bridge is idle. */
39
+ export function buildBridgeConnectUrl(
40
+ environmentId: string,
41
+ ingressUrl?: string,
42
+ ): string {
43
+ const baseUrl = getClaudeAiBaseUrl(undefined, ingressUrl)
44
+ return `${baseUrl}/code?bridge=${environmentId}`
45
+ }
46
+
47
+ /**
48
+ * Build the session URL shown when a session is attached. Delegates to
49
+ * getRemoteSessionUrl for the cse_→session_ prefix translation, then appends
50
+ * the v1-specific ?bridge={environmentId} query.
51
+ */
52
+ export function buildBridgeSessionUrl(
53
+ sessionId: string,
54
+ environmentId: string,
55
+ ingressUrl?: string,
56
+ ): string {
57
+ return `${getRemoteSessionUrl(sessionId, ingressUrl)}?bridge=${environmentId}`
58
+ }
59
+
60
+ /** Compute the glimmer index for a reverse-sweep shimmer animation. */
61
+ export function computeGlimmerIndex(
62
+ tick: number,
63
+ messageWidth: number,
64
+ ): number {
65
+ const cycleLength = messageWidth + 20
66
+ return messageWidth + 10 - (tick % cycleLength)
67
+ }
68
+
69
+ /**
70
+ * Split text into three segments by visual column position for shimmer rendering.
71
+ *
72
+ * Uses grapheme segmentation and `stringWidth` so the split is correct for
73
+ * multi-byte characters, emoji, and CJK glyphs.
74
+ *
75
+ * Returns `{ before, shimmer, after }` strings. Both renderers (chalk in
76
+ * bridgeUI.ts and React/Ink in bridge.tsx) apply their own coloring to
77
+ * these segments.
78
+ */
79
+ export function computeShimmerSegments(
80
+ text: string,
81
+ glimmerIndex: number,
82
+ ): { before: string; shimmer: string; after: string } {
83
+ const messageWidth = stringWidth(text)
84
+ const shimmerStart = glimmerIndex - 1
85
+ const shimmerEnd = glimmerIndex + 1
86
+
87
+ // When shimmer is offscreen, return all text as "before"
88
+ if (shimmerStart >= messageWidth || shimmerEnd < 0) {
89
+ return { before: text, shimmer: '', after: '' }
90
+ }
91
+
92
+ // Split into at most 3 segments by visual column position
93
+ const clampedStart = Math.max(0, shimmerStart)
94
+ let colPos = 0
95
+ let before = ''
96
+ let shimmer = ''
97
+ let after = ''
98
+ for (const { segment } of getGraphemeSegmenter().segment(text)) {
99
+ const segWidth = stringWidth(segment)
100
+ if (colPos + segWidth <= clampedStart) {
101
+ before += segment
102
+ } else if (colPos > shimmerEnd) {
103
+ after += segment
104
+ } else {
105
+ shimmer += segment
106
+ }
107
+ colPos += segWidth
108
+ }
109
+
110
+ return { before, shimmer, after }
111
+ }
112
+
113
+ /** Computed bridge status label and color from connection state. */
114
+ export type BridgeStatusInfo = {
115
+ label:
116
+ | 'Remote Control failed'
117
+ | 'Remote Control reconnecting'
118
+ | 'Remote Control active'
119
+ | 'Remote Control connecting\u2026'
120
+ color: 'error' | 'warning' | 'success'
121
+ }
122
+
123
+ /** Derive a status label and color from the bridge connection state. */
124
+ export function getBridgeStatus({
125
+ error,
126
+ connected,
127
+ sessionActive,
128
+ reconnecting,
129
+ }: {
130
+ error: string | undefined
131
+ connected: boolean
132
+ sessionActive: boolean
133
+ reconnecting: boolean
134
+ }): BridgeStatusInfo {
135
+ if (error) return { label: 'Remote Control failed', color: 'error' }
136
+ if (reconnecting)
137
+ return { label: 'Remote Control reconnecting', color: 'warning' }
138
+ if (sessionActive || connected)
139
+ return { label: 'Remote Control active', color: 'success' }
140
+ return { label: 'Remote Control connecting\u2026', color: 'warning' }
141
+ }
142
+
143
+ /** Footer text shown when bridge is idle (Ready state). */
144
+ export function buildIdleFooterText(url: string): string {
145
+ return `Code everywhere with the Claude app or ${url}`
146
+ }
147
+
148
+ /** Footer text shown when a session is active (Connected state). */
149
+ export function buildActiveFooterText(url: string): string {
150
+ return `Continue coding in the Claude app or ${url}`
151
+ }
152
+
153
+ /** Footer text shown when the bridge has failed. */
154
+ export const FAILED_FOOTER_TEXT = 'Something went wrong, please try again'
155
+
156
+ /**
157
+ * Wrap text in an OSC 8 terminal hyperlink. Zero visual width for layout purposes.
158
+ * strip-ansi (used by stringWidth) correctly strips these sequences, so
159
+ * countVisualLines in bridgeUI.ts remains accurate.
160
+ */
161
+ export function wrapWithOsc8Link(text: string, url: string): string {
162
+ return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`
163
+ }