remodex-cli 1.0.0

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 (99) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +105 -0
  3. package/dist/archive-store.d.ts +28 -0
  4. package/dist/archive-store.js +68 -0
  5. package/dist/archive-store.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +88 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/codex-process.d.ts +186 -0
  10. package/dist/codex-process.js +2111 -0
  11. package/dist/codex-process.js.map +1 -0
  12. package/dist/debug-trace-store.d.ts +15 -0
  13. package/dist/debug-trace-store.js +78 -0
  14. package/dist/debug-trace-store.js.map +1 -0
  15. package/dist/doctor.d.ts +58 -0
  16. package/dist/doctor.js +670 -0
  17. package/dist/doctor.js.map +1 -0
  18. package/dist/firebase-auth.d.ts +35 -0
  19. package/dist/firebase-auth.js +132 -0
  20. package/dist/firebase-auth.js.map +1 -0
  21. package/dist/gallery-store.d.ts +67 -0
  22. package/dist/gallery-store.js +333 -0
  23. package/dist/gallery-store.js.map +1 -0
  24. package/dist/git-assist.d.ts +7 -0
  25. package/dist/git-assist.js +51 -0
  26. package/dist/git-assist.js.map +1 -0
  27. package/dist/git-operations.d.ts +63 -0
  28. package/dist/git-operations.js +292 -0
  29. package/dist/git-operations.js.map +1 -0
  30. package/dist/image-store.d.ts +23 -0
  31. package/dist/image-store.js +142 -0
  32. package/dist/image-store.js.map +1 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +198 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/mdns.d.ts +7 -0
  37. package/dist/mdns.js +49 -0
  38. package/dist/mdns.js.map +1 -0
  39. package/dist/parser.d.ts +620 -0
  40. package/dist/parser.js +423 -0
  41. package/dist/parser.js.map +1 -0
  42. package/dist/path-utils.d.ts +4 -0
  43. package/dist/path-utils.js +34 -0
  44. package/dist/path-utils.js.map +1 -0
  45. package/dist/project-history.d.ts +10 -0
  46. package/dist/project-history.js +73 -0
  47. package/dist/project-history.js.map +1 -0
  48. package/dist/prompt-history-backup.d.ts +15 -0
  49. package/dist/prompt-history-backup.js +46 -0
  50. package/dist/prompt-history-backup.js.map +1 -0
  51. package/dist/proxy.d.ts +15 -0
  52. package/dist/proxy.js +95 -0
  53. package/dist/proxy.js.map +1 -0
  54. package/dist/push-i18n.d.ts +7 -0
  55. package/dist/push-i18n.js +75 -0
  56. package/dist/push-i18n.js.map +1 -0
  57. package/dist/push-relay.d.ts +29 -0
  58. package/dist/push-relay.js +70 -0
  59. package/dist/push-relay.js.map +1 -0
  60. package/dist/recording-store.d.ts +51 -0
  61. package/dist/recording-store.js +158 -0
  62. package/dist/recording-store.js.map +1 -0
  63. package/dist/screenshot.d.ts +28 -0
  64. package/dist/screenshot.js +98 -0
  65. package/dist/screenshot.js.map +1 -0
  66. package/dist/sdk-process.d.ts +180 -0
  67. package/dist/sdk-process.js +960 -0
  68. package/dist/sdk-process.js.map +1 -0
  69. package/dist/session.d.ts +144 -0
  70. package/dist/session.js +687 -0
  71. package/dist/session.js.map +1 -0
  72. package/dist/sessions-index.d.ts +130 -0
  73. package/dist/sessions-index.js +1817 -0
  74. package/dist/sessions-index.js.map +1 -0
  75. package/dist/setup-launchd.d.ts +9 -0
  76. package/dist/setup-launchd.js +115 -0
  77. package/dist/setup-launchd.js.map +1 -0
  78. package/dist/setup-systemd.d.ts +9 -0
  79. package/dist/setup-systemd.js +122 -0
  80. package/dist/setup-systemd.js.map +1 -0
  81. package/dist/startup-info.d.ts +9 -0
  82. package/dist/startup-info.js +116 -0
  83. package/dist/startup-info.js.map +1 -0
  84. package/dist/usage.d.ts +69 -0
  85. package/dist/usage.js +545 -0
  86. package/dist/usage.js.map +1 -0
  87. package/dist/version.d.ts +13 -0
  88. package/dist/version.js +43 -0
  89. package/dist/version.js.map +1 -0
  90. package/dist/websocket.d.ts +132 -0
  91. package/dist/websocket.js +3551 -0
  92. package/dist/websocket.js.map +1 -0
  93. package/dist/worktree-store.d.ts +26 -0
  94. package/dist/worktree-store.js +61 -0
  95. package/dist/worktree-store.js.map +1 -0
  96. package/dist/worktree.d.ts +47 -0
  97. package/dist/worktree.js +330 -0
  98. package/dist/worktree.js.map +1 -0
  99. package/package.json +62 -0
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Global proxy support for Bridge Server fetch() calls.
3
+ *
4
+ * Reads HTTPS_PROXY / HTTP_PROXY / ALL_PROXY and configures the
5
+ * Node.js global fetch dispatcher via undici.
6
+ *
7
+ * Supported protocols:
8
+ * http:// → undici ProxyAgent
9
+ * https:// → undici ProxyAgent
10
+ * socks5:// → SOCKS5 tunnel via undici Agent + socks
11
+ * socks4:// → SOCKS4 tunnel via undici Agent + socks
12
+ *
13
+ * Call setupProxy() once before any fetch() — typically in index.ts / cli.ts.
14
+ */
15
+ export declare function setupProxy(): string | undefined;
package/dist/proxy.js ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Global proxy support for Bridge Server fetch() calls.
3
+ *
4
+ * Reads HTTPS_PROXY / HTTP_PROXY / ALL_PROXY and configures the
5
+ * Node.js global fetch dispatcher via undici.
6
+ *
7
+ * Supported protocols:
8
+ * http:// → undici ProxyAgent
9
+ * https:// → undici ProxyAgent
10
+ * socks5:// → SOCKS5 tunnel via undici Agent + socks
11
+ * socks4:// → SOCKS4 tunnel via undici Agent + socks
12
+ *
13
+ * Call setupProxy() once before any fetch() — typically in index.ts / cli.ts.
14
+ */
15
+ import { setGlobalDispatcher, ProxyAgent, Agent } from "undici";
16
+ import { SocksClient } from "socks";
17
+ import { connect as tlsConnect } from "node:tls";
18
+ function getProxyUrl() {
19
+ return (process.env.HTTPS_PROXY ||
20
+ process.env.https_proxy ||
21
+ process.env.HTTP_PROXY ||
22
+ process.env.http_proxy ||
23
+ process.env.ALL_PROXY ||
24
+ process.env.all_proxy);
25
+ }
26
+ export function setupProxy() {
27
+ const proxyUrl = getProxyUrl();
28
+ if (!proxyUrl)
29
+ return undefined;
30
+ let parsed;
31
+ try {
32
+ parsed = new URL(proxyUrl);
33
+ }
34
+ catch {
35
+ console.warn(`[proxy] Invalid proxy URL: ${proxyUrl}`);
36
+ return undefined;
37
+ }
38
+ const proto = parsed.protocol.replace(":", "").toLowerCase();
39
+ if (proto === "http" || proto === "https") {
40
+ setGlobalDispatcher(new ProxyAgent({ uri: proxyUrl }));
41
+ }
42
+ else if (proto === "socks5" || proto === "socks5h" || proto === "socks4" || proto === "socks") {
43
+ setupSocks(parsed, proto);
44
+ }
45
+ else {
46
+ console.warn(`[proxy] Unsupported protocol: ${proto}`);
47
+ return undefined;
48
+ }
49
+ console.log(`[proxy] Using ${proto} proxy ${parsed.hostname}:${parsed.port}`);
50
+ return proxyUrl;
51
+ }
52
+ function setupSocks(parsed, proto) {
53
+ const proxy = {
54
+ host: parsed.hostname,
55
+ port: parseInt(parsed.port, 10) || 1080,
56
+ type: proto.startsWith("socks4") ? 4 : 5,
57
+ };
58
+ if (parsed.username) {
59
+ proxy.userId = decodeURIComponent(parsed.username);
60
+ }
61
+ if (parsed.password) {
62
+ proxy.password = decodeURIComponent(parsed.password);
63
+ }
64
+ setGlobalDispatcher(new Agent({
65
+ connect: async (opts, cb) => {
66
+ try {
67
+ const host = typeof opts.hostname === "string"
68
+ ? opts.hostname
69
+ : (opts.host ?? "localhost");
70
+ const port = typeof opts.port === "number"
71
+ ? opts.port
72
+ : parseInt(String(opts.port ?? "443"), 10);
73
+ const { socket } = await SocksClient.createConnection({
74
+ proxy,
75
+ command: "connect",
76
+ destination: { host, port },
77
+ });
78
+ if (opts.protocol === "https:" || port === 443) {
79
+ const tls = tlsConnect({
80
+ socket: socket,
81
+ servername: host,
82
+ });
83
+ cb(null, tls);
84
+ }
85
+ else {
86
+ cb(null, socket);
87
+ }
88
+ }
89
+ catch (err) {
90
+ cb(err instanceof Error ? err : new Error(String(err)), null);
91
+ }
92
+ },
93
+ }));
94
+ }
95
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type PushLocale = "en" | "ja" | "zh";
2
+ export declare function normalizePushLocale(locale: string | undefined): PushLocale;
3
+ /**
4
+ * Look up a translated push notification string.
5
+ * Supports `{param}` placeholders replaced by `params` values.
6
+ */
7
+ export declare function t(locale: PushLocale, key: string, params?: Record<string, string>): string;
@@ -0,0 +1,75 @@
1
+ const translations = {
2
+ en: {
3
+ approval_title: "Approval needed",
4
+ ask_title: "Response needed",
5
+ plan_ready_title: "Plan ready",
6
+ approval_body: "Approve execution of {toolName}",
7
+ plan_ready_body: "Plan is ready for review",
8
+ ask_default_body: "Claude is asking a question",
9
+ task_completed: "Task completed",
10
+ error_occurred: "Error occurred",
11
+ session_completed: "Session completed",
12
+ session_failed: "Session failed",
13
+ // Privacy mode: generic bodies without tool names, question text, or result details
14
+ approval_body_private: "Approve tool execution",
15
+ ask_body_private: "Please respond to a question",
16
+ result_success_body_private: "Session completed",
17
+ result_error_body_private: "Session failed",
18
+ },
19
+ ja: {
20
+ approval_title: "承認待ち",
21
+ ask_title: "回答待ち",
22
+ plan_ready_title: "プラン完成",
23
+ approval_body: "{toolName} の実行を承認してください",
24
+ plan_ready_body: "プランが完成しました。確認してください",
25
+ ask_default_body: "Claude が質問しています",
26
+ task_completed: "タスク完了",
27
+ error_occurred: "エラー発生",
28
+ session_completed: "セッション完了",
29
+ session_failed: "セッションが失敗しました",
30
+ // Privacy mode
31
+ approval_body_private: "ツールの実行を承認してください",
32
+ ask_body_private: "質問に回答してください",
33
+ result_success_body_private: "セッション完了",
34
+ result_error_body_private: "セッションが失敗しました",
35
+ },
36
+ zh: {
37
+ approval_title: "需要批准",
38
+ ask_title: "需要回复",
39
+ plan_ready_title: "计划已准备好",
40
+ approval_body: "请批准执行 {toolName}",
41
+ plan_ready_body: "计划已准备好,请查看",
42
+ ask_default_body: "Claude 正在提问",
43
+ task_completed: "任务已完成",
44
+ error_occurred: "发生错误",
45
+ session_completed: "会话已完成",
46
+ session_failed: "会话失败",
47
+ // Privacy mode
48
+ approval_body_private: "请批准工具执行",
49
+ ask_body_private: "请回答一个问题",
50
+ result_success_body_private: "会话已完成",
51
+ result_error_body_private: "会话失败",
52
+ },
53
+ };
54
+ const SUPPORTED_LOCALES = new Set(["en", "ja", "zh"]);
55
+ export function normalizePushLocale(locale) {
56
+ if (!locale)
57
+ return "en";
58
+ const lang = locale.split(/[-_]/)[0].toLowerCase();
59
+ return SUPPORTED_LOCALES.has(lang) ? lang : "en";
60
+ }
61
+ /**
62
+ * Look up a translated push notification string.
63
+ * Supports `{param}` placeholders replaced by `params` values.
64
+ */
65
+ export function t(locale, key, params) {
66
+ const table = translations[locale] ?? translations.en;
67
+ let text = table[key] ?? translations.en[key] ?? key;
68
+ if (params) {
69
+ for (const [k, v] of Object.entries(params)) {
70
+ text = text.replaceAll(`{${k}}`, v);
71
+ }
72
+ }
73
+ return text;
74
+ }
75
+ //# sourceMappingURL=push-i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-i18n.js","sourceRoot":"","sources":["../src/push-i18n.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAA+C;IAC/D,EAAE,EAAE;QACF,cAAc,EAAE,iBAAiB;QACjC,SAAS,EAAE,iBAAiB;QAC5B,gBAAgB,EAAE,YAAY;QAC9B,aAAa,EAAE,iCAAiC;QAChD,eAAe,EAAE,0BAA0B;QAC3C,gBAAgB,EAAE,6BAA6B;QAC/C,cAAc,EAAE,gBAAgB;QAChC,cAAc,EAAE,gBAAgB;QAChC,iBAAiB,EAAE,mBAAmB;QACtC,cAAc,EAAE,gBAAgB;QAChC,oFAAoF;QACpF,qBAAqB,EAAE,wBAAwB;QAC/C,gBAAgB,EAAE,8BAA8B;QAChD,2BAA2B,EAAE,mBAAmB;QAChD,yBAAyB,EAAE,gBAAgB;KAC5C;IACD,EAAE,EAAE;QACF,cAAc,EAAE,MAAM;QACtB,SAAS,EAAE,MAAM;QACjB,gBAAgB,EAAE,OAAO;QACzB,aAAa,EAAE,yBAAyB;QACxC,eAAe,EAAE,qBAAqB;QACtC,gBAAgB,EAAE,iBAAiB;QACnC,cAAc,EAAE,OAAO;QACvB,cAAc,EAAE,OAAO;QACvB,iBAAiB,EAAE,SAAS;QAC5B,cAAc,EAAE,cAAc;QAC9B,eAAe;QACf,qBAAqB,EAAE,iBAAiB;QACxC,gBAAgB,EAAE,aAAa;QAC/B,2BAA2B,EAAE,SAAS;QACtC,yBAAyB,EAAE,cAAc;KAC1C;IACD,EAAE,EAAE;QACF,cAAc,EAAE,MAAM;QACtB,SAAS,EAAE,MAAM;QACjB,gBAAgB,EAAE,QAAQ;QAC1B,aAAa,EAAE,kBAAkB;QACjC,eAAe,EAAE,YAAY;QAC7B,gBAAgB,EAAE,aAAa;QAC/B,cAAc,EAAE,OAAO;QACvB,cAAc,EAAE,MAAM;QACtB,iBAAiB,EAAE,OAAO;QAC1B,cAAc,EAAE,MAAM;QACtB,eAAe;QACf,qBAAqB,EAAE,SAAS;QAChC,gBAAgB,EAAE,SAAS;QAC3B,2BAA2B,EAAE,OAAO;QACpC,yBAAyB,EAAE,MAAM;KAClC;CACF,CAAC;AAEF,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAE9D,MAAM,UAAU,mBAAmB,CAAC,MAA0B;IAC5D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACnD,OAAO,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAmB,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,CAAC,CACf,MAAkB,EAClB,GAAW,EACX,MAA+B;IAE/B,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,EAAE,CAAC;IACtD,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;IACrD,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5C,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { FirebaseAuthClient } from "./firebase-auth.js";
2
+ export type PushPlatform = "ios" | "android" | "web";
3
+ export interface PushNotifyPayload {
4
+ eventType: string;
5
+ title: string;
6
+ body: string;
7
+ /** When set, only tokens registered with this locale receive the notification. */
8
+ locale?: string;
9
+ data?: Record<string, string>;
10
+ }
11
+ export interface PushRelayClientOptions {
12
+ relayUrl?: string;
13
+ firebaseAuth?: FirebaseAuthClient | null;
14
+ timeoutMs?: number;
15
+ fetchImpl?: typeof fetch;
16
+ }
17
+ export declare class PushRelayClient {
18
+ private readonly relayUrl;
19
+ private readonly firebaseAuth;
20
+ private readonly timeoutMs;
21
+ private readonly fetchImpl;
22
+ constructor(options?: PushRelayClientOptions);
23
+ get isConfigured(): boolean;
24
+ private get bridgeId();
25
+ registerToken(token: string, platform: PushPlatform, locale?: string): Promise<void>;
26
+ unregisterToken(token: string): Promise<void>;
27
+ notify(payload: PushNotifyPayload): Promise<void>;
28
+ private post;
29
+ }
@@ -0,0 +1,70 @@
1
+ const DEFAULT_RELAY_URL = "https://us-central1-remodex-ca33b.cloudfunctions.net/relay";
2
+ export class PushRelayClient {
3
+ relayUrl;
4
+ firebaseAuth;
5
+ timeoutMs;
6
+ fetchImpl;
7
+ constructor(options = {}) {
8
+ this.relayUrl = options.relayUrl ?? DEFAULT_RELAY_URL;
9
+ this.firebaseAuth = options.firebaseAuth ?? null;
10
+ this.timeoutMs = options.timeoutMs ?? 10_000;
11
+ this.fetchImpl = options.fetchImpl ?? fetch;
12
+ }
13
+ get isConfigured() {
14
+ return this.firebaseAuth != null;
15
+ }
16
+ get bridgeId() {
17
+ return this.firebaseAuth.uid;
18
+ }
19
+ async registerToken(token, platform, locale) {
20
+ if (!this.isConfigured)
21
+ return;
22
+ await this.post({ op: "register", token, platform, locale, bridgeId: this.bridgeId });
23
+ }
24
+ async unregisterToken(token) {
25
+ if (!this.isConfigured)
26
+ return;
27
+ await this.post({ op: "unregister", token, bridgeId: this.bridgeId });
28
+ }
29
+ async notify(payload) {
30
+ if (!this.isConfigured)
31
+ return;
32
+ await this.post({
33
+ op: "notify",
34
+ bridgeId: this.bridgeId,
35
+ eventType: payload.eventType,
36
+ title: payload.title,
37
+ body: payload.body,
38
+ locale: payload.locale,
39
+ data: payload.data,
40
+ });
41
+ }
42
+ async post(payload) {
43
+ if (!this.isConfigured || !this.firebaseAuth)
44
+ return;
45
+ console.log(`[push-relay] ${payload.op} → ${this.relayUrl} (bridgeId: ${payload.bridgeId})`);
46
+ const idToken = await this.firebaseAuth.getIdToken();
47
+ const controller = new AbortController();
48
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
49
+ try {
50
+ const response = await this.fetchImpl(this.relayUrl, {
51
+ method: "POST",
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ "Authorization": `Bearer ${idToken}`,
55
+ },
56
+ body: JSON.stringify(payload),
57
+ signal: controller.signal,
58
+ });
59
+ const responseText = (await response.text()).trim().slice(0, 200);
60
+ if (!response.ok) {
61
+ throw new Error(`Push relay returned ${response.status}${responseText ? `: ${responseText}` : ""}`);
62
+ }
63
+ console.log(`[push-relay] ${payload.op} OK: ${responseText}`);
64
+ }
65
+ finally {
66
+ clearTimeout(timer);
67
+ }
68
+ }
69
+ }
70
+ //# sourceMappingURL=push-relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-relay.js","sourceRoot":"","sources":["../src/push-relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { ClientMessage, ServerMessage } from "./parser.js";
2
+ export interface RecordingMeta {
3
+ bridgeSessionId: string;
4
+ claudeSessionId?: string;
5
+ projectPath: string;
6
+ createdAt: string;
7
+ }
8
+ export interface RecordingFileInfo {
9
+ name: string;
10
+ path: string;
11
+ modified: string;
12
+ sizeBytes: number;
13
+ meta?: RecordingMeta;
14
+ summary?: string;
15
+ firstPrompt?: string;
16
+ lastPrompt?: string;
17
+ }
18
+ export interface RecordedEvent {
19
+ ts: string;
20
+ direction: "outgoing" | "incoming";
21
+ message: ServerMessage | ClientMessage;
22
+ }
23
+ export declare class RecordingStore {
24
+ private recordingDir;
25
+ private writeChains;
26
+ constructor(rootDir?: string);
27
+ init(): Promise<void>;
28
+ getFilePath(sessionId: string): string;
29
+ private getMetaPath;
30
+ /** Save or update session metadata alongside the recording. */
31
+ saveMeta(sessionId: string, meta: RecordingMeta): void;
32
+ /** Read session metadata. */
33
+ getMeta(sessionId: string): Promise<RecordingMeta | null>;
34
+ record(sessionId: string, direction: "outgoing" | "incoming", message: ServerMessage | ClientMessage): void;
35
+ /** List all recording files, newest first, with metadata if available. */
36
+ listRecordings(): Promise<RecordingFileInfo[]>;
37
+ /**
38
+ * Extract key info directly from JSONL content without requiring meta or sessions-index.
39
+ * Scans for first/last user input messages and claudeSessionId from system messages.
40
+ */
41
+ extractInfoFromJsonl(sessionId: string): Promise<{
42
+ firstPrompt?: string;
43
+ lastPrompt?: string;
44
+ claudeSessionId?: string;
45
+ projectPath?: string;
46
+ }>;
47
+ /** Read recording content as string. */
48
+ getRecordingContent(sessionId: string): Promise<string | null>;
49
+ flush(): Promise<void>;
50
+ private enqueue;
51
+ }
@@ -0,0 +1,158 @@
1
+ import { appendFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ const DEFAULT_ROOT_DIR = join(homedir(), ".remodex", "debug");
5
+ const RECORDING_DIRNAME = "recordings";
6
+ export class RecordingStore {
7
+ recordingDir;
8
+ writeChains = new Map();
9
+ constructor(rootDir = DEFAULT_ROOT_DIR) {
10
+ this.recordingDir = join(rootDir, RECORDING_DIRNAME);
11
+ }
12
+ async init() {
13
+ await mkdir(this.recordingDir, { recursive: true });
14
+ }
15
+ getFilePath(sessionId) {
16
+ return join(this.recordingDir, `${sanitizeSegment(sessionId)}.jsonl`);
17
+ }
18
+ getMetaPath(sessionId) {
19
+ return join(this.recordingDir, `${sanitizeSegment(sessionId)}.meta.json`);
20
+ }
21
+ /** Save or update session metadata alongside the recording. */
22
+ saveMeta(sessionId, meta) {
23
+ const path = this.getMetaPath(sessionId);
24
+ this.enqueue(path, async () => {
25
+ await writeFile(path, JSON.stringify(meta, null, 2), "utf-8");
26
+ });
27
+ }
28
+ /** Read session metadata. */
29
+ async getMeta(sessionId) {
30
+ try {
31
+ const raw = await readFile(this.getMetaPath(sessionId), "utf-8");
32
+ return JSON.parse(raw);
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ record(sessionId, direction, message) {
39
+ const path = this.getFilePath(sessionId);
40
+ const event = {
41
+ ts: new Date().toISOString(),
42
+ direction,
43
+ message,
44
+ };
45
+ const line = `${JSON.stringify(event)}\n`;
46
+ this.enqueue(path, async () => {
47
+ await appendFile(path, line, "utf-8");
48
+ });
49
+ }
50
+ /** List all recording files, newest first, with metadata if available. */
51
+ async listRecordings() {
52
+ try {
53
+ const entries = await readdir(this.recordingDir);
54
+ const results = [];
55
+ for (const entry of entries) {
56
+ if (!entry.endsWith(".jsonl"))
57
+ continue;
58
+ const name = entry.replace(/\.jsonl$/, "");
59
+ const filePath = join(this.recordingDir, entry);
60
+ const s = await stat(filePath);
61
+ const meta = await this.getMeta(name);
62
+ results.push({
63
+ name,
64
+ path: filePath,
65
+ modified: s.mtime.toISOString(),
66
+ sizeBytes: s.size,
67
+ ...(meta ? { meta } : {}),
68
+ });
69
+ }
70
+ results.sort((a, b) => b.modified.localeCompare(a.modified));
71
+ return results;
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ /**
78
+ * Extract key info directly from JSONL content without requiring meta or sessions-index.
79
+ * Scans for first/last user input messages and claudeSessionId from system messages.
80
+ */
81
+ async extractInfoFromJsonl(sessionId) {
82
+ try {
83
+ const content = await readFile(this.getFilePath(sessionId), "utf-8");
84
+ const lines = content.trim().split("\n");
85
+ let firstPrompt;
86
+ let lastPrompt;
87
+ let claudeSessionId;
88
+ let projectPath;
89
+ for (const line of lines) {
90
+ try {
91
+ const event = JSON.parse(line);
92
+ const msg = event.message;
93
+ // Extract user input messages (incoming "input" type)
94
+ if (event.direction === "incoming" && msg.type === "input" && typeof msg.text === "string") {
95
+ if (!firstPrompt)
96
+ firstPrompt = msg.text;
97
+ lastPrompt = msg.text;
98
+ }
99
+ // Extract claudeSessionId from system messages
100
+ if (event.direction === "outgoing" && msg.type === "system" && typeof msg.sessionId === "string") {
101
+ // Claude CLI session IDs are full UUIDs (36 chars)
102
+ const sid = msg.sessionId;
103
+ if (sid.length > 8 && !claudeSessionId) {
104
+ claudeSessionId = sid;
105
+ }
106
+ }
107
+ // Extract projectPath from start messages
108
+ if (event.direction === "incoming" && msg.type === "start" && typeof msg.projectPath === "string") {
109
+ projectPath = msg.projectPath;
110
+ }
111
+ }
112
+ catch {
113
+ // skip malformed lines
114
+ }
115
+ }
116
+ return { firstPrompt, lastPrompt, claudeSessionId, projectPath };
117
+ }
118
+ catch {
119
+ return {};
120
+ }
121
+ }
122
+ /** Read recording content as string. */
123
+ async getRecordingContent(sessionId) {
124
+ try {
125
+ const filePath = this.getFilePath(sessionId);
126
+ return await readFile(filePath, "utf-8");
127
+ }
128
+ catch {
129
+ return null;
130
+ }
131
+ }
132
+ async flush() {
133
+ const pendingWrites = [...this.writeChains.values()];
134
+ await Promise.all(pendingWrites.map((p) => p.catch(() => { })));
135
+ }
136
+ enqueue(path, task) {
137
+ const previous = this.writeChains.get(path) ?? Promise.resolve();
138
+ const next = previous
139
+ .catch(() => { })
140
+ .then(async () => {
141
+ await mkdir(dirname(path), { recursive: true });
142
+ await task();
143
+ })
144
+ .finally(() => {
145
+ if (this.writeChains.get(path) === next) {
146
+ this.writeChains.delete(path);
147
+ }
148
+ });
149
+ this.writeChains.set(path, next);
150
+ void next.catch((err) => {
151
+ console.warn(`[recording-store] Failed to write ${path}:`, err);
152
+ });
153
+ }
154
+ }
155
+ function sanitizeSegment(value) {
156
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
157
+ }
158
+ //# sourceMappingURL=recording-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-store.js","sourceRoot":"","sources":["../src/recording-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Screenshot module — macOS native screenshot capture via CLI commands.
3
+ *
4
+ * - `listWindows()`: Enumerate on-screen windows using CGWindowListCopyWindowInfo
5
+ * via Swift's CoreGraphics (Quartz PyObjC is unreliable across Python installs).
6
+ * - `takeScreenshot()`: Capture full-screen or a specific window via `screencapture`.
7
+ */
8
+ export interface WindowBounds {
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ }
14
+ export interface WindowInfo {
15
+ windowId: number;
16
+ ownerName: string;
17
+ windowTitle: string;
18
+ bounds: WindowBounds;
19
+ }
20
+ export interface ScreenshotOptions {
21
+ mode: "fullscreen" | "window";
22
+ windowId?: number;
23
+ }
24
+ export interface ScreenshotResult {
25
+ filePath: string;
26
+ }
27
+ export declare function listWindows(): Promise<WindowInfo[]>;
28
+ export declare function takeScreenshot(options: ScreenshotOptions): Promise<ScreenshotResult>;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Screenshot module — macOS native screenshot capture via CLI commands.
3
+ *
4
+ * - `listWindows()`: Enumerate on-screen windows using CGWindowListCopyWindowInfo
5
+ * via Swift's CoreGraphics (Quartz PyObjC is unreliable across Python installs).
6
+ * - `takeScreenshot()`: Capture full-screen or a specific window via `screencapture`.
7
+ */
8
+ import { execFile } from "node:child_process";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ // ---------------------------------------------------------------------------
12
+ // listWindows
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Swift inline script that calls CGWindowListCopyWindowInfo and outputs JSON.
16
+ * Uses `swift -e` which is available on any macOS with Xcode CLT.
17
+ * Python + Quartz was unreliable (PyObjC missing on non-system python installs).
18
+ */
19
+ const LIST_WINDOWS_SWIFT = `
20
+ import CoreGraphics
21
+ import Foundation
22
+
23
+ let windowList = CGWindowListCopyWindowInfo(
24
+ [.optionOnScreenOnly, .excludeDesktopElements],
25
+ kCGNullWindowID
26
+ ) as? [[String: Any]] ?? []
27
+
28
+ var out: [[String: Any]] = []
29
+ for w in windowList {
30
+ guard let layer = w[kCGWindowLayer as String] as? Int, layer == 0 else { continue }
31
+ guard let owner = w[kCGWindowOwnerName as String] as? String, !owner.isEmpty else { continue }
32
+ let bounds = w[kCGWindowBounds as String] as? [String: Any] ?? [:]
33
+ let width = bounds["Width"] as? Double ?? 0
34
+ let height = bounds["Height"] as? Double ?? 0
35
+ if width < 50 || height < 50 { continue }
36
+ out.append([
37
+ "windowId": w[kCGWindowNumber as String] as? Int ?? 0,
38
+ "ownerName": owner,
39
+ "windowTitle": (w[kCGWindowName as String] as? String) ?? "",
40
+ "bounds": [
41
+ "x": (bounds["X"] as? Double ?? 0) as Any,
42
+ "y": (bounds["Y"] as? Double ?? 0) as Any,
43
+ "width": width as Any,
44
+ "height": height as Any,
45
+ ] as Any,
46
+ ])
47
+ }
48
+ let data = try JSONSerialization.data(withJSONObject: out, options: [])
49
+ print(String(data: data, encoding: .utf8)!)
50
+ `;
51
+ export async function listWindows() {
52
+ if (process.platform !== "darwin") {
53
+ throw new Error("listWindows is only supported on macOS");
54
+ }
55
+ return new Promise((resolve, reject) => {
56
+ execFile("swift", ["-e", LIST_WINDOWS_SWIFT], { timeout: 15_000 }, (err, stdout, stderr) => {
57
+ if (err) {
58
+ reject(new Error(`Failed to list windows: ${err.message}${stderr ? ` — ${stderr}` : ""}`));
59
+ return;
60
+ }
61
+ try {
62
+ const windows = JSON.parse(stdout);
63
+ resolve(windows);
64
+ }
65
+ catch (parseErr) {
66
+ reject(new Error(`Failed to parse window list: ${parseErr}`));
67
+ }
68
+ });
69
+ });
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // takeScreenshot
73
+ // ---------------------------------------------------------------------------
74
+ export async function takeScreenshot(options) {
75
+ if (process.platform !== "darwin") {
76
+ throw new Error("takeScreenshot is only supported on macOS");
77
+ }
78
+ const filePath = join(tmpdir(), `remodex-screenshot-${Date.now()}.png`);
79
+ const args = ["-x"]; // silent (no capture sound)
80
+ if (options.mode === "window") {
81
+ if (options.windowId == null) {
82
+ throw new Error("windowId is required for window mode");
83
+ }
84
+ args.push("-o"); // no window shadow
85
+ args.push("-l", String(options.windowId));
86
+ }
87
+ args.push("-t", "png", filePath);
88
+ return new Promise((resolve, reject) => {
89
+ execFile("screencapture", args, { timeout: 10_000 }, (err, _stdout, stderr) => {
90
+ if (err) {
91
+ reject(new Error(`screencapture failed: ${err.message}${stderr ? ` — ${stderr}` : ""}`));
92
+ return;
93
+ }
94
+ resolve({ filePath });
95
+ });
96
+ });
97
+ }
98
+ //# sourceMappingURL=screenshot.js.map