@prestyj/cli 4.10.2 → 4.11.4

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 (96) hide show
  1. package/README.md +4 -4
  2. package/dist/app-sidecar.d.ts +2 -0
  3. package/dist/app-sidecar.d.ts.map +1 -0
  4. package/dist/app-sidecar.js +1407 -0
  5. package/dist/app-sidecar.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +5 -22
  8. package/dist/cli.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +120 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +412 -6
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/auth-providers.d.ts +18 -0
  14. package/dist/core/auth-providers.d.ts.map +1 -0
  15. package/dist/core/auth-providers.js +71 -0
  16. package/dist/core/auth-providers.js.map +1 -0
  17. package/dist/core/event-bus.d.ts +6 -0
  18. package/dist/core/event-bus.d.ts.map +1 -1
  19. package/dist/core/event-bus.js +1 -0
  20. package/dist/core/event-bus.js.map +1 -1
  21. package/dist/core/project-discovery.d.ts +41 -0
  22. package/dist/core/project-discovery.d.ts.map +1 -0
  23. package/dist/core/project-discovery.js +441 -0
  24. package/dist/core/project-discovery.js.map +1 -0
  25. package/dist/core/prompt-commands.d.ts.map +1 -1
  26. package/dist/core/prompt-commands.js +91 -0
  27. package/dist/core/prompt-commands.js.map +1 -1
  28. package/dist/core/prompt-commands.test.js +17 -0
  29. package/dist/core/prompt-commands.test.js.map +1 -1
  30. package/dist/core/radio.d.ts +44 -0
  31. package/dist/core/radio.d.ts.map +1 -0
  32. package/dist/core/radio.js +250 -0
  33. package/dist/core/radio.js.map +1 -0
  34. package/dist/core/resolve-start.d.ts +29 -0
  35. package/dist/core/resolve-start.d.ts.map +1 -0
  36. package/dist/core/resolve-start.js +35 -0
  37. package/dist/core/resolve-start.js.map +1 -0
  38. package/dist/core/resolve-start.test.d.ts +2 -0
  39. package/dist/core/resolve-start.test.d.ts.map +1 -0
  40. package/dist/core/resolve-start.test.js +66 -0
  41. package/dist/core/resolve-start.test.js.map +1 -0
  42. package/dist/core/settings-manager.d.ts +1 -1
  43. package/dist/core/shell-path.d.ts +12 -0
  44. package/dist/core/shell-path.d.ts.map +1 -0
  45. package/dist/core/shell-path.js +166 -0
  46. package/dist/core/shell-path.js.map +1 -0
  47. package/dist/core/slash-commands.d.ts.map +1 -1
  48. package/dist/core/slash-commands.js +10 -0
  49. package/dist/core/slash-commands.js.map +1 -1
  50. package/dist/core/telegram-config.d.ts +21 -0
  51. package/dist/core/telegram-config.d.ts.map +1 -0
  52. package/dist/core/telegram-config.js +52 -0
  53. package/dist/core/telegram-config.js.map +1 -0
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +2 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/modes/serve-mode.d.ts +24 -0
  59. package/dist/modes/serve-mode.d.ts.map +1 -1
  60. package/dist/modes/serve-mode.js +54 -19
  61. package/dist/modes/serve-mode.js.map +1 -1
  62. package/dist/tools/goal-mode.test.js +1 -1
  63. package/dist/tools/goal-mode.test.js.map +1 -1
  64. package/dist/tools/goals.d.ts +7 -7
  65. package/dist/tools/index.d.ts +3 -0
  66. package/dist/tools/index.d.ts.map +1 -1
  67. package/dist/tools/index.js +1 -1
  68. package/dist/tools/index.js.map +1 -1
  69. package/dist/tools/subagent.d.ts +2 -4
  70. package/dist/tools/subagent.d.ts.map +1 -1
  71. package/dist/tools/subagent.js +24 -23
  72. package/dist/tools/subagent.js.map +1 -1
  73. package/dist/tools/tasks.d.ts +1 -1
  74. package/dist/ui/hooks/useTranscriptHistory.d.ts +2 -0
  75. package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -1
  76. package/dist/ui/hooks/useTranscriptHistory.js +3 -2
  77. package/dist/ui/hooks/useTranscriptHistory.js.map +1 -1
  78. package/dist/ui/long-prompt-regression-harness.test.js +8 -8
  79. package/dist/ui/render.d.ts.map +1 -1
  80. package/dist/ui/render.js +60 -8
  81. package/dist/ui/render.js.map +1 -1
  82. package/dist/ui/startup-banner-order.test.js +1 -1
  83. package/dist/ui/startup-banner-order.test.js.map +1 -1
  84. package/dist/ui/terminal-history-repaint.test.d.ts +2 -0
  85. package/dist/ui/terminal-history-repaint.test.d.ts.map +1 -0
  86. package/dist/ui/terminal-history-repaint.test.js +73 -0
  87. package/dist/ui/terminal-history-repaint.test.js.map +1 -0
  88. package/dist/ui/terminal-history.d.ts +1 -0
  89. package/dist/ui/terminal-history.d.ts.map +1 -1
  90. package/dist/ui/terminal-history.js +31 -2
  91. package/dist/ui/terminal-history.js.map +1 -1
  92. package/dist/utils/git.d.ts +6 -0
  93. package/dist/utils/git.d.ts.map +1 -1
  94. package/dist/utils/git.js +12 -0
  95. package/dist/utils/git.js.map +1 -1
  96. package/package.json +5 -5
@@ -0,0 +1,18 @@
1
+ export type AuthMethod = "oauth" | "apikey";
2
+ export interface AuthProviderMeta {
3
+ /** Stable provider id (matches the gg-ai Provider union, plus storage keys). */
4
+ value: string;
5
+ /** Display name shown in the login list. */
6
+ label: string;
7
+ /** One-line model summary. */
8
+ description: string;
9
+ /** Supported auth methods, in preferred order (oauth first when both). */
10
+ methods: AuthMethod[];
11
+ /** Friendly label for the API key field (e.g. "Z.AI"). */
12
+ apiKeyLabel?: string;
13
+ /** Fixed base URL stored alongside an API key (e.g. Xiaomi's token plan). */
14
+ apiKeyBaseUrl?: string;
15
+ }
16
+ export declare const AUTH_PROVIDERS: AuthProviderMeta[];
17
+ export declare function getAuthProvider(value: string): AuthProviderMeta | undefined;
18
+ //# sourceMappingURL=auth-providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-providers.d.ts","sourceRoot":"","sources":["../../src/core/auth-providers.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE5C,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,EAAE,gBAAgB,EA8D5C,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE3E"}
@@ -0,0 +1,71 @@
1
+ // Single source of truth for the providers the login UI offers and how each
2
+ // authenticates. Mirrors the CLI's `ezcoder login` provider list (ui/login.tsx)
3
+ // so the desktop app and the terminal stay in lockstep. The app fetches this
4
+ // (plus live connection status) from the sidecar's /auth/status endpoint.
5
+ export const AUTH_PROVIDERS = [
6
+ {
7
+ value: "anthropic",
8
+ label: "Anthropic",
9
+ description: "Claude Opus 4.8, Sonnet 4.6, Haiku 4.5",
10
+ methods: ["oauth"],
11
+ },
12
+ {
13
+ value: "openai",
14
+ label: "OpenAI",
15
+ description: "GPT-5.5, GPT-5.5 Pro, GPT-5.4, GPT-5.3 Codex",
16
+ methods: ["oauth"],
17
+ },
18
+ {
19
+ value: "gemini",
20
+ label: "Gemini",
21
+ description: "Gemini 3.1 Flash Lite Preview",
22
+ methods: ["oauth"],
23
+ },
24
+ {
25
+ value: "moonshot",
26
+ label: "Moonshot",
27
+ description: "Kimi K2.7 · OAuth or API key",
28
+ methods: ["oauth", "apikey"],
29
+ apiKeyLabel: "Moonshot",
30
+ },
31
+ {
32
+ value: "glm",
33
+ label: "Z.AI (GLM)",
34
+ description: "GLM-5.1, GLM-4.7, GLM-4.7 Flash",
35
+ methods: ["apikey"],
36
+ apiKeyLabel: "Z.AI",
37
+ },
38
+ {
39
+ value: "minimax",
40
+ label: "MiniMax",
41
+ description: "MiniMax M3",
42
+ methods: ["apikey"],
43
+ apiKeyLabel: "MiniMax",
44
+ },
45
+ {
46
+ value: "xiaomi",
47
+ label: "Xiaomi (MiMo)",
48
+ description: "MiMo-V2-Pro",
49
+ methods: ["apikey"],
50
+ apiKeyLabel: "Xiaomi MiMo",
51
+ apiKeyBaseUrl: "https://token-plan-sgp.xiaomimimo.com/v1",
52
+ },
53
+ {
54
+ value: "deepseek",
55
+ label: "DeepSeek",
56
+ description: "DeepSeek V4 Pro, V4 Flash",
57
+ methods: ["apikey"],
58
+ apiKeyLabel: "DeepSeek",
59
+ },
60
+ {
61
+ value: "openrouter",
62
+ label: "OpenRouter",
63
+ description: "Qwen3.6-Plus, multi-provider gateway",
64
+ methods: ["apikey"],
65
+ apiKeyLabel: "OpenRouter",
66
+ },
67
+ ];
68
+ export function getAuthProvider(value) {
69
+ return AUTH_PROVIDERS.find((p) => p.value === value);
70
+ }
71
+ //# sourceMappingURL=auth-providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-providers.js","sourceRoot":"","sources":["../../src/core/auth-providers.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,0EAA0E;AAmB1E,MAAM,CAAC,MAAM,cAAc,GAAuB;IAChD;QACE,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,wCAAwC;QACrD,OAAO,EAAE,CAAC,OAAO,CAAC;KACnB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,8CAA8C;QAC3D,OAAO,EAAE,CAAC,OAAO,CAAC;KACnB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,+BAA+B;QAC5C,OAAO,EAAE,CAAC,OAAO,CAAC;KACnB;IACD;QACE,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,8BAA8B;QAC3C,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;QAC5B,WAAW,EAAE,UAAU;KACxB;IACD;QACE,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,iCAAiC;QAC9C,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,MAAM;KACpB;IACD;QACE,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,YAAY;QACzB,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,SAAS;KACvB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,aAAa;QAC1B,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,aAAa;QAC1B,aAAa,EAAE,0CAA0C;KAC1D;IACD;QACE,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,2BAA2B;QACxC,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,UAAU;KACxB;IACD;QACE,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,sCAAsC;QACnD,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,YAAY;KAC1B;CACF,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;AACvD,CAAC"}
@@ -20,6 +20,8 @@ export interface BusEventMap {
20
20
  result: string;
21
21
  isError: boolean;
22
22
  durationMs: number;
23
+ /** Tool-specific extras (e.g. screenshot/read image previews). */
24
+ details?: unknown;
23
25
  };
24
26
  turn_end: {
25
27
  turn: number;
@@ -53,12 +55,16 @@ export interface BusEventMap {
53
55
  resultType: string;
54
56
  data: unknown;
55
57
  };
58
+ hook: {
59
+ kind: "ideal" | "loop_break" | "regrounding";
60
+ };
56
61
  session_start: {
57
62
  sessionId: string;
58
63
  };
59
64
  model_change: {
60
65
  provider: string;
61
66
  model: string;
67
+ supportsVideo?: boolean;
62
68
  };
63
69
  compaction_start: {
64
70
  messageCount: number;
@@ -1 +1 @@
1
- {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/core/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD,MAAM,WAAW,WAAW;IAE1B,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7B,cAAc,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,eAAe,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IACrF,gBAAgB,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAC1D,aAAa,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5F,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAC;YAAC,UAAU,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC/F,CAAC;IACF,UAAU,EAAE;QACV,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE;YACV,WAAW,EAAE,MAAM,CAAC;YACpB,YAAY,EAAE,MAAM,CAAC;YACrB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,UAAU,CAAC,EAAE,MAAM,CAAC;SACrB,CAAC;KACH,CAAC;IACF,KAAK,EAAE;QAAE,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC;IAGxB,gBAAgB,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/D,kBAAkB,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IAG7E,aAAa,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,YAAY,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,gBAAgB,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,cAAc,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAG5D,cAAc,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAGzD,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,aAAa,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,KAAK,QAAQ,GAAG,MAAM,WAAW,CAAC;AAClC,KAAK,YAAY,CAAC,CAAC,SAAS,QAAQ,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAI1E,qBAAa,QAAQ;IAEnB,OAAO,CAAC,SAAS,CAAoD;IAErE,EAAE,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAQtE,GAAG,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI;IAIjE,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI;IAQjE,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAQxE,2EAA2E;IAC3E,kBAAkB,IAAI,IAAI;IAO1B,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CA6D3C"}
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/core/event-bus.ts"],"names":[],"mappings":"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"}
@@ -61,6 +61,7 @@ export class EventBus {
61
61
  result: event.result,
62
62
  isError: event.isError,
63
63
  durationMs: event.durationMs,
64
+ details: event.details,
64
65
  });
65
66
  break;
66
67
  case "turn_end":
@@ -1 +1 @@
1
- {"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../src/core/event-bus.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../src/core/event-bus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export type ProjectSource = "ezcoder" | "claude-code" | "codex";
2
+ export interface DiscoveredProject {
3
+ name: string;
4
+ path: string;
5
+ lastActiveMs: number;
6
+ lastActiveDisplay: string;
7
+ /** Sorted, deduped list of stores this project showed up in. */
8
+ sources: ProjectSource[];
9
+ }
10
+ /**
11
+ * Scan ezcoder + Claude Code + Codex session stores and return one row per
12
+ * project, sorted most-recent first. Duplicates (same cwd) are collapsed; the
13
+ * `sources` field lists every store the project appeared in so the picker can
14
+ * show a combined badge.
15
+ */
16
+ export declare function discoverProjects(): Promise<DiscoveredProject[]>;
17
+ export interface RecentSession {
18
+ /** Session id. */
19
+ id: string;
20
+ /** Absolute path to the session .jsonl (passed back to reopen it). */
21
+ path: string;
22
+ /** First user message, trimmed to a short preview (may be empty). */
23
+ preview: string;
24
+ /** Relative "3h ago" string from last activity. */
25
+ lastActiveDisplay: string;
26
+ messageCount: number;
27
+ }
28
+ /**
29
+ * List the most recent ezcoder sessions for a project cwd, newest first, each
30
+ * with a short preview built from its first user message. Used by the new-window
31
+ * project picker to offer "resume a session" alongside "new session".
32
+ *
33
+ * Fast path: instead of fully reading every session file in the project (what
34
+ * SessionManager.list does to count messages), sort files by mtime and read
35
+ * only the newest `limit`. Each chosen file is parsed in ONE pass for its
36
+ * header id, message count, last activity, and first user preview. For projects
37
+ * with many/large sessions this is the difference between scanning everything
38
+ * and scanning ~5 files.
39
+ */
40
+ export declare function listRecentSessions(cwd: string, limit?: number): Promise<RecentSession[]>;
41
+ //# sourceMappingURL=project-discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-discovery.d.ts","sourceRoot":"","sources":["../../src/core/project-discovery.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA6BrE;AAsSD,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAczF"}
@@ -0,0 +1,441 @@
1
+ import fs from "node:fs/promises";
2
+ import { createReadStream } from "node:fs";
3
+ import readline from "node:readline";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { getAppPaths } from "../config.js";
7
+ /**
8
+ * Scan ezcoder + Claude Code + Codex session stores and return one row per
9
+ * project, sorted most-recent first. Duplicates (same cwd) are collapsed; the
10
+ * `sources` field lists every store the project appeared in so the picker can
11
+ * show a combined badge.
12
+ */
13
+ export async function discoverProjects() {
14
+ const [gg, cc, cx] = await Promise.all([
15
+ discoverGgcoderProjects(),
16
+ discoverClaudeProjects(),
17
+ discoverCodexProjects(),
18
+ ]);
19
+ const byPath = new Map();
20
+ for (const p of [...gg, ...cc, ...cx]) {
21
+ const existing = byPath.get(p.path);
22
+ if (!existing) {
23
+ byPath.set(p.path, p);
24
+ continue;
25
+ }
26
+ byPath.set(p.path, {
27
+ name: existing.name,
28
+ path: existing.path,
29
+ lastActiveMs: Math.max(existing.lastActiveMs, p.lastActiveMs),
30
+ lastActiveDisplay: "", // recomputed below
31
+ sources: mergeSources(existing.sources, p.sources),
32
+ });
33
+ }
34
+ const merged = Array.from(byPath.values()).map((p) => ({
35
+ ...p,
36
+ lastActiveDisplay: formatRelativeTime(p.lastActiveMs),
37
+ }));
38
+ merged.sort((a, b) => b.lastActiveMs - a.lastActiveMs);
39
+ return merged;
40
+ }
41
+ const SOURCE_ORDER = {
42
+ ezcoder: 0,
43
+ "claude-code": 1,
44
+ codex: 2,
45
+ };
46
+ function mergeSources(a, b) {
47
+ const set = new Set([...a, ...b]);
48
+ return Array.from(set).sort((x, y) => SOURCE_ORDER[x] - SOURCE_ORDER[y]);
49
+ }
50
+ /**
51
+ * Scan ~/.ezcoder/sessions/. Each session directory's name is the encoded cwd
52
+ * (slashes → underscores); we decode it back and verify the directory still
53
+ * exists on disk.
54
+ */
55
+ async function discoverGgcoderProjects() {
56
+ const sessionsDir = getAppPaths().sessionsDir;
57
+ let entries;
58
+ try {
59
+ entries = await fs.readdir(sessionsDir);
60
+ }
61
+ catch {
62
+ return [];
63
+ }
64
+ const results = [];
65
+ for (const entry of entries) {
66
+ const dir = path.join(sessionsDir, entry);
67
+ const mtime = await maxJsonlMtime(dir);
68
+ if (mtime === null)
69
+ continue;
70
+ // Normalize so paths containing traversal segments (e.g. an agent launched
71
+ // with cwd `.../src-tauri/../..`) collapse to their real directory and the
72
+ // basename isn't a stray "..". Deduped against other entries downstream.
73
+ const decoded = path.resolve("/" + entry.replace(/_/g, "/"));
74
+ if (!(await isDirectory(decoded)))
75
+ continue;
76
+ results.push({
77
+ name: path.basename(decoded),
78
+ path: decoded,
79
+ lastActiveMs: mtime,
80
+ lastActiveDisplay: formatRelativeTime(mtime),
81
+ sources: ["ezcoder"],
82
+ });
83
+ }
84
+ return results;
85
+ }
86
+ /**
87
+ * Scan ~/.claude/projects/. Claude Code's directory encoding replaces every
88
+ * "/" with "-", which is genuinely ambiguous — a real dash in a path component
89
+ * (e.g. "ezcoder") collides with the separator. So we extract the cwd from
90
+ * the JSONL events themselves; Claude writes it into user/assistant records.
91
+ * Falls back to a best-effort dash decode only if no event carries a cwd.
92
+ */
93
+ async function discoverClaudeProjects() {
94
+ const projectsDir = path.join(os.homedir(), ".claude", "projects");
95
+ let entries;
96
+ try {
97
+ entries = await fs.readdir(projectsDir);
98
+ }
99
+ catch {
100
+ return [];
101
+ }
102
+ const results = await Promise.all(entries.map(async (entry) => {
103
+ const dir = path.join(projectsDir, entry);
104
+ const mtime = await maxJsonlMtime(dir);
105
+ if (mtime === null)
106
+ return null;
107
+ const cwd = (await readFirstFromJsonlDir(dir, claudeCwdExtractor)) ?? fallbackDashDecode(entry);
108
+ if (!cwd)
109
+ return null;
110
+ if (!(await isDirectory(cwd)))
111
+ return null;
112
+ return {
113
+ name: path.basename(cwd),
114
+ path: cwd,
115
+ lastActiveMs: mtime,
116
+ lastActiveDisplay: formatRelativeTime(mtime),
117
+ sources: ["claude-code"],
118
+ };
119
+ }));
120
+ return results.filter((p) => p !== null);
121
+ }
122
+ /**
123
+ * Scan ~/.codex/sessions/. Codex stores sessions flat by date
124
+ * (`YYYY/MM/DD/rollout-*.jsonl`) with the cwd embedded in the first user
125
+ * message as `<environment_context><cwd>/abs/path</cwd>...</environment_context>`.
126
+ * We group sessions by extracted cwd and take max mtime per group.
127
+ */
128
+ async function discoverCodexProjects() {
129
+ const sessionsDir = path.join(os.homedir(), ".codex", "sessions");
130
+ if (!(await isDirectory(sessionsDir)))
131
+ return [];
132
+ // Layout is YYYY/MM/DD/*.jsonl — depth 4 covers it.
133
+ const files = await collectJsonlFiles(sessionsDir, 4);
134
+ if (files.length === 0)
135
+ return [];
136
+ // Process newest first so per-cwd we always start with the latest mtime.
137
+ files.sort((a, b) => b.mtime - a.mtime);
138
+ const byCwd = new Map();
139
+ for (const f of files) {
140
+ const cwd = await readFirstFromFile(f.path, codexCwdExtractor);
141
+ if (!cwd)
142
+ continue;
143
+ const prev = byCwd.get(cwd);
144
+ if (prev === undefined || f.mtime > prev)
145
+ byCwd.set(cwd, f.mtime);
146
+ }
147
+ const results = [];
148
+ for (const [cwd, mtime] of byCwd) {
149
+ if (!(await isDirectory(cwd)))
150
+ continue;
151
+ results.push({
152
+ name: path.basename(cwd),
153
+ path: cwd,
154
+ lastActiveMs: mtime,
155
+ lastActiveDisplay: formatRelativeTime(mtime),
156
+ sources: ["codex"],
157
+ });
158
+ }
159
+ return results;
160
+ }
161
+ async function isDirectory(p) {
162
+ try {
163
+ const s = await fs.stat(p);
164
+ return s.isDirectory();
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ }
170
+ async function maxJsonlMtime(dir) {
171
+ if (!(await isDirectory(dir)))
172
+ return null;
173
+ const files = await collectJsonlFiles(dir, 2);
174
+ if (files.length === 0)
175
+ return null;
176
+ let max = 0;
177
+ for (const f of files)
178
+ if (f.mtime > max)
179
+ max = f.mtime;
180
+ return max > 0 ? max : null;
181
+ }
182
+ /**
183
+ * Walk `dir` up to `maxDepth` levels deep collecting every .jsonl file. Used
184
+ * for both Claude Code (top-level + `<uuid>/subagents/`) and Codex
185
+ * (`YYYY/MM/DD/`) layouts.
186
+ */
187
+ async function collectJsonlFiles(dir, maxDepth) {
188
+ const out = [];
189
+ await walk(dir, 0);
190
+ return out;
191
+ async function walk(current, depth) {
192
+ let entries;
193
+ try {
194
+ entries = await fs.readdir(current, { withFileTypes: true });
195
+ }
196
+ catch {
197
+ return;
198
+ }
199
+ for (const e of entries) {
200
+ const full = path.join(current, e.name);
201
+ if (e.isFile() && e.name.endsWith(".jsonl")) {
202
+ try {
203
+ const s = await fs.stat(full);
204
+ out.push({ path: full, mtime: s.mtimeMs });
205
+ }
206
+ catch {
207
+ // skip unreadable
208
+ }
209
+ }
210
+ else if (e.isDirectory() && depth < maxDepth) {
211
+ await walk(full, depth + 1);
212
+ }
213
+ }
214
+ }
215
+ }
216
+ const claudeCwdExtractor = (line) => {
217
+ try {
218
+ const parsed = JSON.parse(line);
219
+ if (typeof parsed.cwd === "string" && parsed.cwd.startsWith("/"))
220
+ return parsed.cwd;
221
+ }
222
+ catch {
223
+ // skip malformed
224
+ }
225
+ return null;
226
+ };
227
+ const CODEX_CWD_RE = /<cwd>([^<]+)<\/cwd>/;
228
+ const codexCwdExtractor = (line) => {
229
+ // Current format (openai/codex protocol.rs, late-2025+): RolloutLine wraps
230
+ // SessionMeta / TurnContext items with `{ type, payload: { cwd, ... } }`.
231
+ // First line is always SessionMeta, so this hits on read 1.
232
+ try {
233
+ const parsed = JSON.parse(line);
234
+ const cwd = parsed.payload?.cwd;
235
+ if (typeof cwd === "string" && cwd.startsWith("/"))
236
+ return cwd;
237
+ }
238
+ catch {
239
+ // not JSON or unexpected shape; fall through to legacy regex
240
+ }
241
+ // Legacy format (pre-late-2025): cwd embedded as <cwd>...</cwd> inside an
242
+ // <environment_context> user-message string.
243
+ const m = CODEX_CWD_RE.exec(line);
244
+ if (m && m[1] && m[1].startsWith("/"))
245
+ return m[1];
246
+ return null;
247
+ };
248
+ /**
249
+ * Walk all .jsonl files under `dir` newest-first, returning the first non-null
250
+ * extractor result. Walks two levels deep (matches Claude Code's nested
251
+ * layout).
252
+ */
253
+ async function readFirstFromJsonlDir(dir, extractor) {
254
+ const files = await collectJsonlFiles(dir, 2);
255
+ if (files.length === 0)
256
+ return null;
257
+ files.sort((a, b) => b.mtime - a.mtime);
258
+ for (const f of files) {
259
+ const v = await readFirstFromFile(f.path, extractor);
260
+ if (v)
261
+ return v;
262
+ }
263
+ return null;
264
+ }
265
+ /**
266
+ * Stream `file` line-by-line and return the first non-null extractor result.
267
+ * Caps lines so a giant transcript can't stall discovery.
268
+ */
269
+ async function readFirstFromFile(file, extractor) {
270
+ return new Promise((resolve) => {
271
+ const stream = createReadStream(file, { encoding: "utf-8" });
272
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
273
+ let lines = 0;
274
+ let done = false;
275
+ const MAX_LINES = 200;
276
+ // Resolve before tearing down. rl.close() synchronously emits 'close',
277
+ // and if the close handler resolves first our real value gets swallowed.
278
+ const finish = (value) => {
279
+ if (done)
280
+ return;
281
+ done = true;
282
+ resolve(value);
283
+ rl.close();
284
+ stream.destroy();
285
+ };
286
+ rl.on("line", (line) => {
287
+ if (done)
288
+ return;
289
+ lines++;
290
+ if (lines > MAX_LINES) {
291
+ finish(null);
292
+ return;
293
+ }
294
+ const v = extractor(line);
295
+ if (v)
296
+ finish(v);
297
+ });
298
+ rl.on("close", () => finish(null));
299
+ rl.on("error", () => finish(null));
300
+ stream.on("error", () => finish(null));
301
+ });
302
+ }
303
+ function fallbackDashDecode(entry) {
304
+ // Strip leading "-" then turn remaining "-" into "/". Lossy by design — only
305
+ // used when the JSONLs have no cwd events; the caller still verifies the
306
+ // result is an existing directory.
307
+ if (!entry.startsWith("-"))
308
+ return null;
309
+ return "/" + entry.slice(1).replace(/-/g, "/");
310
+ }
311
+ function formatRelativeTime(ms) {
312
+ if (ms === 0)
313
+ return "—";
314
+ const diff = Date.now() - ms;
315
+ if (diff < 60_000)
316
+ return "just now";
317
+ const min = 60_000;
318
+ const hour = 60 * min;
319
+ const day = 24 * hour;
320
+ const week = 7 * day;
321
+ const month = 30 * day;
322
+ if (diff < hour)
323
+ return `${Math.floor(diff / min)}m ago`;
324
+ if (diff < day)
325
+ return `${Math.floor(diff / hour)}h ago`;
326
+ if (diff < week)
327
+ return `${Math.floor(diff / day)}d ago`;
328
+ if (diff < month)
329
+ return `${Math.floor(diff / week)}w ago`;
330
+ return `${Math.floor(diff / month)}mo ago`;
331
+ }
332
+ /**
333
+ * List the most recent ezcoder sessions for a project cwd, newest first, each
334
+ * with a short preview built from its first user message. Used by the new-window
335
+ * project picker to offer "resume a session" alongside "new session".
336
+ *
337
+ * Fast path: instead of fully reading every session file in the project (what
338
+ * SessionManager.list does to count messages), sort files by mtime and read
339
+ * only the newest `limit`. Each chosen file is parsed in ONE pass for its
340
+ * header id, message count, last activity, and first user preview. For projects
341
+ * with many/large sessions this is the difference between scanning everything
342
+ * and scanning ~5 files.
343
+ */
344
+ export async function listRecentSessions(cwd, limit = 5) {
345
+ const sessionsDir = getAppPaths().sessionsDir;
346
+ const dir = path.join(sessionsDir, encodeCwdForSessions(cwd));
347
+ const files = await collectJsonlFiles(dir, 1);
348
+ if (files.length === 0)
349
+ return [];
350
+ files.sort((a, b) => b.mtime - a.mtime);
351
+ const out = [];
352
+ for (const f of files) {
353
+ if (out.length >= limit)
354
+ break;
355
+ const parsed = await readSessionSummary(f.path);
356
+ if (parsed && parsed.messageCount > 0)
357
+ out.push(parsed);
358
+ }
359
+ return out;
360
+ }
361
+ /**
362
+ * Encode a cwd to its session directory name. Mirrors SessionManager's encoding
363
+ * (leading slash dropped, remaining slashes → underscores) so we resolve the
364
+ * same per-project folder without instantiating the manager.
365
+ */
366
+ function encodeCwdForSessions(cwd) {
367
+ return cwd.replace(/[\\/]/g, "_").replace(/:/g, "").replace(/^_/, "");
368
+ }
369
+ /** Single-pass parse of one session file: header id + count + activity + preview. */
370
+ async function readSessionSummary(file) {
371
+ return new Promise((resolve) => {
372
+ const stream = createReadStream(file, { encoding: "utf-8" });
373
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
374
+ let id = "";
375
+ let messageCount = 0;
376
+ let lastActivity = "";
377
+ let preview = "";
378
+ let valid = false;
379
+ let done = false;
380
+ const finish = () => {
381
+ if (done)
382
+ return;
383
+ done = true;
384
+ resolve(valid
385
+ ? { id, path: file, preview, lastActiveDisplay: rel(lastActivity), messageCount }
386
+ : null);
387
+ rl.close();
388
+ stream.destroy();
389
+ };
390
+ rl.on("line", (line) => {
391
+ if (done || !line)
392
+ return;
393
+ try {
394
+ const p = JSON.parse(line);
395
+ if (!valid) {
396
+ if (p.type !== "session")
397
+ return finish(); // not a session file
398
+ valid = true;
399
+ id = p.id ?? "";
400
+ if (p.timestamp)
401
+ lastActivity = p.timestamp;
402
+ return;
403
+ }
404
+ if (p.type === "message") {
405
+ messageCount++;
406
+ if (p.timestamp)
407
+ lastActivity = p.timestamp;
408
+ if (!preview && p.message?.role === "user") {
409
+ const text = extractText(p.message.content);
410
+ if (text)
411
+ preview = text.replace(/\s+/g, " ").trim().slice(0, 80);
412
+ }
413
+ }
414
+ }
415
+ catch {
416
+ // skip malformed line
417
+ }
418
+ });
419
+ rl.on("close", finish);
420
+ rl.on("error", finish);
421
+ stream.on("error", finish);
422
+ });
423
+ }
424
+ function rel(timestamp) {
425
+ return formatRelativeTime(Date.parse(timestamp) || 0);
426
+ }
427
+ function extractText(content) {
428
+ if (typeof content === "string")
429
+ return content;
430
+ if (Array.isArray(content)) {
431
+ for (const block of content) {
432
+ if (block && typeof block === "object" && "text" in block) {
433
+ const t = block.text;
434
+ if (typeof t === "string")
435
+ return t;
436
+ }
437
+ }
438
+ }
439
+ return "";
440
+ }
441
+ //# sourceMappingURL=project-discovery.js.map