minecodex 0.1.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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/apps/dashboard/app.js +387 -0
  4. package/apps/dashboard/favicon.svg +18 -0
  5. package/apps/dashboard/index.html +70 -0
  6. package/apps/dashboard/styles.css +297 -0
  7. package/features/images/README.md +106 -0
  8. package/features/images/codex-feature.json +24 -0
  9. package/features/images/src/http-server.mjs +302 -0
  10. package/features/images/src/image-library.mjs +582 -0
  11. package/features/images/src/main.mjs +52 -0
  12. package/features/images/src/prompt-index.mjs +182 -0
  13. package/features/images/src/save-as.mjs +40 -0
  14. package/features/images/src/thread-metadata.mjs +93 -0
  15. package/features/images/web/app.js +1045 -0
  16. package/features/images/web/index.html +162 -0
  17. package/features/images/web/styles.css +994 -0
  18. package/features/model-slider/README.md +30 -0
  19. package/features/model-slider/THIRD_PARTY_NOTICES.md +24 -0
  20. package/features/model-slider/assets/providers/anthropic.svg +1 -0
  21. package/features/model-slider/assets/providers/deepseek.svg +1 -0
  22. package/features/model-slider/assets/providers/gemini.svg +1 -0
  23. package/features/model-slider/assets/providers/grok.svg +1 -0
  24. package/features/model-slider/assets/providers/hunyuan.svg +1 -0
  25. package/features/model-slider/assets/providers/kimi.svg +1 -0
  26. package/features/model-slider/assets/providers/minimax.svg +1 -0
  27. package/features/model-slider/assets/providers/openai.svg +1 -0
  28. package/features/model-slider/assets/providers/qwen.svg +1 -0
  29. package/features/model-slider/assets/providers/xiaomimimo.svg +1 -0
  30. package/features/model-slider/assets/providers/zhipu.svg +1 -0
  31. package/features/model-slider/assets/ui/sparkles.svg +18 -0
  32. package/features/model-slider/assets/ui/star-off.svg +17 -0
  33. package/features/model-slider/assets/ui/star.svg +15 -0
  34. package/features/model-slider/codex-feature.json +56 -0
  35. package/features/model-slider/vendor/cc-switch-LICENSE +21 -0
  36. package/features/model-slider/vendor/lucide-LICENSE +35 -0
  37. package/features/notes/README.md +106 -0
  38. package/features/notes/assets/icons/circle.svg +15 -0
  39. package/features/notes/assets/icons/ellipsis.svg +17 -0
  40. package/features/notes/assets/icons/external-link.svg +17 -0
  41. package/features/notes/assets/icons/list-todo.svg +19 -0
  42. package/features/notes/assets/icons/message-square-quote.svg +17 -0
  43. package/features/notes/assets/icons/paperclip.svg +15 -0
  44. package/features/notes/assets/icons/star-off.svg +17 -0
  45. package/features/notes/assets/icons/star.svg +15 -0
  46. package/features/notes/assets/icons/sticky-note.svg +16 -0
  47. package/features/notes/assets/icons/trash-2.svg +19 -0
  48. package/features/notes/codex-feature.json +90 -0
  49. package/features/notes/src/http-server.mjs +541 -0
  50. package/features/notes/src/main.mjs +54 -0
  51. package/features/notes/src/product.mjs +771 -0
  52. package/features/notes/web/app.js +1495 -0
  53. package/features/notes/web/codex-artifact-document.svg +1 -0
  54. package/features/notes/web/codex-attachment.svg +1 -0
  55. package/features/notes/web/codex-build.svg +1 -0
  56. package/features/notes/web/codex-code.svg +1 -0
  57. package/features/notes/web/codex-cplusplus.svg +1 -0
  58. package/features/notes/web/codex-css.svg +1 -0
  59. package/features/notes/web/codex-document.svg +1 -0
  60. package/features/notes/web/codex-file.svg +1 -0
  61. package/features/notes/web/codex-folder.svg +1 -0
  62. package/features/notes/web/codex-hashes.svg +1 -0
  63. package/features/notes/web/codex-html.svg +1 -0
  64. package/features/notes/web/codex-image.svg +1 -0
  65. package/features/notes/web/codex-java.svg +1 -0
  66. package/features/notes/web/codex-javascript.svg +1 -0
  67. package/features/notes/web/codex-json.svg +1 -0
  68. package/features/notes/web/codex-notebook.svg +1 -0
  69. package/features/notes/web/codex-pdf.svg +1 -0
  70. package/features/notes/web/codex-pencil.svg +3 -0
  71. package/features/notes/web/codex-php.svg +1 -0
  72. package/features/notes/web/codex-plus.svg +3 -0
  73. package/features/notes/web/codex-presentation.svg +1 -0
  74. package/features/notes/web/codex-python.svg +1 -0
  75. package/features/notes/web/codex-react.svg +1 -0
  76. package/features/notes/web/codex-rust.svg +1 -0
  77. package/features/notes/web/codex-shell.svg +1 -0
  78. package/features/notes/web/codex-skill.svg +1 -0
  79. package/features/notes/web/codex-spreadsheet.svg +1 -0
  80. package/features/notes/web/codex-task.svg +3 -0
  81. package/features/notes/web/codex-terminal.svg +1 -0
  82. package/features/notes/web/codex-toml.svg +1 -0
  83. package/features/notes/web/codex-typescript.svg +1 -0
  84. package/features/notes/web/codex-view-all.svg +3 -0
  85. package/features/notes/web/index.html +15 -0
  86. package/features/notes/web/styles.css +1155 -0
  87. package/package.json +67 -0
  88. package/packages/cli/bin/mcx.mjs +7 -0
  89. package/packages/cli/src/browser-exchange.mjs +31 -0
  90. package/packages/cli/src/commands.mjs +389 -0
  91. package/packages/cli/src/config.mjs +101 -0
  92. package/packages/cli/src/control-server.mjs +371 -0
  93. package/packages/cli/src/npm-adapter.mjs +40 -0
  94. package/packages/cli/src/paths.mjs +33 -0
  95. package/packages/cli/src/platform.mjs +368 -0
  96. package/packages/cli/src/runtime-manager.mjs +338 -0
  97. package/packages/runtime-host/README.md +171 -0
  98. package/packages/runtime-host/src/codex-runtime.mjs +4311 -0
  99. package/packages/runtime-host/src/feature-registry.mjs +827 -0
  100. package/packages/runtime-host/src/main.mjs +98 -0
  101. package/packages/runtime-host/src/pid-file.mjs +23 -0
@@ -0,0 +1,182 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { readdir, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { createInterface } from "node:readline";
5
+
6
+ const DEFAULT_NEGATIVE_CACHE_TTL_MS = 5_000;
7
+
8
+ async function findThreadLogs(directory, threadId, output = []) {
9
+ let entries;
10
+ try {
11
+ entries = await readdir(directory, { withFileTypes: true });
12
+ } catch (error) {
13
+ if (error.code === "ENOENT") return output;
14
+ throw error;
15
+ }
16
+
17
+ for (const entry of entries) {
18
+ const entryPath = path.join(directory, entry.name);
19
+ if (entry.isDirectory()) await findThreadLogs(entryPath, threadId, output);
20
+ else if (entry.isFile() && entry.name.endsWith(`${threadId}.jsonl`)) output.push(entryPath);
21
+ }
22
+ return output;
23
+ }
24
+
25
+ function promptFromEventLine(line) {
26
+ if (!line.includes('"image_generation_end"')) return null;
27
+ try {
28
+ const event = JSON.parse(line);
29
+ const payload = event?.payload ?? event;
30
+ if (payload?.type !== "image_generation_end") return null;
31
+ if (typeof payload.call_id !== "string" || typeof payload.revised_prompt !== "string") return null;
32
+ return { imageGenerationItemId: payload.call_id, prompt: payload.revised_prompt };
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ async function readPrompts(logPath, prompts) {
39
+ const input = createReadStream(logPath, { encoding: "utf8" });
40
+ const lines = createInterface({ input, crlfDelay: Infinity });
41
+ for await (const line of lines) {
42
+ const entry = promptFromEventLine(line);
43
+ if (entry && !prompts.has(entry.imageGenerationItemId)) {
44
+ prompts.set(entry.imageGenerationItemId, entry.prompt);
45
+ }
46
+ }
47
+ }
48
+
49
+ function cacheKey(threadId, imageGenerationItemId) {
50
+ return `${threadId}\u0000${imageGenerationItemId}`;
51
+ }
52
+
53
+ async function fileSignature(logPath) {
54
+ try {
55
+ const info = await stat(logPath);
56
+ return { mtimeMs: info.mtimeMs, size: info.size };
57
+ } catch (error) {
58
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") return null;
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ function sameSignature(left, right) {
64
+ return left?.mtimeMs === right?.mtimeMs && left?.size === right?.size;
65
+ }
66
+
67
+ export class ThreadPromptIndex {
68
+ constructor({ sessionRoots = [], negativeTtlMs = DEFAULT_NEGATIVE_CACHE_TTL_MS, now = Date.now } = {}) {
69
+ if (!Number.isFinite(negativeTtlMs) || negativeTtlMs < 0) {
70
+ throw new Error("negativeTtlMs must be a non-negative number");
71
+ }
72
+ if (typeof now !== "function") throw new Error("now must be a function");
73
+ this.sessionRoots = sessionRoots.map((sessionRoot) => path.resolve(sessionRoot));
74
+ this.negativeTtlMs = negativeTtlMs;
75
+ this.now = now;
76
+ this.threadPrompts = new Map();
77
+ this.threadLogPaths = new Map();
78
+ this.threadLogSignatures = new Map();
79
+ this.scanPromises = new Map();
80
+ this.negativeCache = new Map();
81
+ this.scanCount = 0;
82
+ }
83
+
84
+ async scanThread(threadId, { refreshPaths = false } = {}) {
85
+ this.scanCount += 1;
86
+ if (refreshPaths || !this.threadLogPaths.has(threadId)) {
87
+ const logPaths = [];
88
+ for (const sessionRoot of this.sessionRoots) {
89
+ await findThreadLogs(sessionRoot, threadId, logPaths);
90
+ }
91
+ this.threadLogPaths.set(threadId, logPaths);
92
+ }
93
+ const logPaths = this.threadLogPaths.get(threadId) ?? [];
94
+ const prompts = new Map();
95
+ const signatures = new Map();
96
+ for (const logPath of logPaths) {
97
+ await readPrompts(logPath, prompts);
98
+ const signature = await fileSignature(logPath);
99
+ if (signature) signatures.set(logPath, signature);
100
+ }
101
+ this.threadPrompts.set(threadId, prompts);
102
+ this.threadLogSignatures.set(threadId, signatures);
103
+ return prompts;
104
+ }
105
+
106
+ async threadLogsChanged(threadId) {
107
+ const logPaths = this.threadLogPaths.get(threadId) ?? [];
108
+ const signatures = this.threadLogSignatures.get(threadId) ?? new Map();
109
+ for (const logPath of logPaths) {
110
+ const current = await fileSignature(logPath);
111
+ if (!current || !sameSignature(current, signatures.get(logPath))) return true;
112
+ }
113
+ return false;
114
+ }
115
+
116
+ clearNegative(threadId) {
117
+ for (const key of this.negativeCache.keys()) {
118
+ if (key.startsWith(`${threadId}\u0000`)) this.negativeCache.delete(key);
119
+ }
120
+ }
121
+
122
+ async find(threadId, imageGenerationItemId) {
123
+ const key = cacheKey(threadId, imageGenerationItemId);
124
+ const cached = this.threadPrompts.get(threadId);
125
+ if (cached?.has(imageGenerationItemId)) return cached.get(imageGenerationItemId);
126
+
127
+ const negative = this.negativeCache.get(key);
128
+ if (negative) {
129
+ const expired = this.now() >= negative.expiresAt;
130
+ const changed = expired || await this.threadLogsChanged(threadId);
131
+ if (!changed) return null;
132
+ this.negativeCache.delete(key);
133
+ this.threadPrompts.delete(threadId);
134
+ this.threadLogSignatures.delete(threadId);
135
+ this.clearNegative(threadId);
136
+ if (expired) this.threadLogPaths.delete(threadId);
137
+ }
138
+
139
+ let scan = this.scanPromises.get(threadId);
140
+ if (!scan) {
141
+ scan = this.scanThread(threadId, { refreshPaths: !this.threadLogPaths.has(threadId) });
142
+ this.scanPromises.set(threadId, scan);
143
+ const clearScan = () => {
144
+ if (this.scanPromises.get(threadId) === scan) this.scanPromises.delete(threadId);
145
+ };
146
+ scan.then(clearScan, clearScan);
147
+ }
148
+ const prompts = await scan;
149
+ const prompt = prompts.get(imageGenerationItemId) ?? null;
150
+ if (!prompt) {
151
+ this.negativeCache.set(key, { expiresAt: this.now() + this.negativeTtlMs });
152
+ }
153
+ return prompt;
154
+ }
155
+
156
+ invalidate(threadId = null) {
157
+ if (threadId == null) {
158
+ this.threadPrompts.clear();
159
+ this.threadLogPaths.clear();
160
+ this.threadLogSignatures.clear();
161
+ this.negativeCache.clear();
162
+ return;
163
+ }
164
+ this.threadPrompts.delete(threadId);
165
+ this.threadLogPaths.delete(threadId);
166
+ this.threadLogSignatures.delete(threadId);
167
+ this.clearNegative(threadId);
168
+ }
169
+
170
+ close() {
171
+ this.threadPrompts.clear();
172
+ this.threadLogPaths.clear();
173
+ this.threadLogSignatures.clear();
174
+ this.scanPromises.clear();
175
+ this.negativeCache.clear();
176
+ }
177
+ }
178
+
179
+ export async function findImagePrompt({ sessionRoots, threadId, imageGenerationItemId }) {
180
+ const index = new ThreadPromptIndex({ sessionRoots });
181
+ return index.find(threadId, imageGenerationItemId);
182
+ }
@@ -0,0 +1,40 @@
1
+ import { execFile } from "node:child_process";
2
+ import { copyFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+
6
+ const run = promisify(execFile);
7
+ const SAVE_DIALOG_SCRIPT = `
8
+ on run argv
9
+ try
10
+ set destinationFile to choose file name with prompt "Save image as" default name (item 1 of argv)
11
+ return POSIX path of destinationFile
12
+ on error number -128
13
+ return ""
14
+ end try
15
+ end run
16
+ `;
17
+
18
+ export function defaultImageFilename(threadTitle) {
19
+ const rawTitle = String(threadTitle || "Generated image").replace(/\.png$/i, "");
20
+ const safeTitle = rawTitle
21
+ .replace(/[\\/:*?"<>|\u0000-\u001f]/g, "-")
22
+ .replace(/\s+/g, " ")
23
+ .replace(/^\.+|\.+$/g, "")
24
+ .trim()
25
+ .slice(0, 96);
26
+ return `${safeTitle || "Generated image"}.png`;
27
+ }
28
+
29
+ export async function saveImageAs(image) {
30
+ const { stdout } = await run(
31
+ "/usr/bin/osascript",
32
+ ["-e", SAVE_DIALOG_SCRIPT, defaultImageFilename(image.threadTitle)],
33
+ { encoding: "utf8" },
34
+ );
35
+ const destinationPath = stdout.trim();
36
+ if (!destinationPath) return { saved: false, canceled: true };
37
+
38
+ await copyFile(image.sourcePath, destinationPath);
39
+ return { saved: true, fileName: path.basename(destinationPath) };
40
+ }
@@ -0,0 +1,93 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+
5
+ function parseSessionIndex(text) {
6
+ const names = new Map();
7
+ for (const line of text.split("\n")) {
8
+ if (!line.trim()) continue;
9
+ const entry = JSON.parse(line);
10
+ if (entry.id && entry.thread_name) names.set(entry.id, entry.thread_name);
11
+ }
12
+ return names;
13
+ }
14
+
15
+ function projectContaining(projects, cwd) {
16
+ if (!cwd) return null;
17
+ return projects
18
+ .flatMap((project) => (project.rootPaths ?? []).map((rootPath) => ({ project, rootPath })))
19
+ .filter(({ rootPath }) => cwd === rootPath || cwd.startsWith(`${rootPath}${path.sep}`))
20
+ .sort((left, right) => right.rootPath.length - left.rootPath.length)[0]?.project ?? null;
21
+ }
22
+
23
+ export class ThreadMetadataIndex {
24
+ constructor({ stateDatabasePath, globalStatePath, sessionIndexPath }) {
25
+ this.stateDatabasePath = stateDatabasePath;
26
+ this.globalStatePath = globalStatePath;
27
+ this.sessionIndexPath = sessionIndexPath;
28
+ this.database = null;
29
+ this.threadNames = new Map();
30
+ this.projectlessThreadIds = new Set();
31
+ this.projects = new Map();
32
+ this.projectAssignments = {};
33
+ this.refreshPromise = null;
34
+ }
35
+
36
+ async refresh() {
37
+ if (this.refreshPromise) return this.refreshPromise;
38
+ this.refreshPromise = (async () => {
39
+ const [globalStateText, sessionIndexText] = await Promise.all([
40
+ readFile(this.globalStatePath, "utf8"),
41
+ readFile(this.sessionIndexPath, "utf8"),
42
+ ]);
43
+ const globalState = JSON.parse(globalStateText);
44
+ this.threadNames = parseSessionIndex(sessionIndexText);
45
+ this.projectlessThreadIds = new Set(globalState["projectless-thread-ids"] ?? []);
46
+ this.projects = new Map(
47
+ Object.entries(globalState["local-projects"] ?? {})
48
+ .map(([projectId, project]) => [projectId, { ...project, id: project.id ?? projectId }]),
49
+ );
50
+ this.projectAssignments = globalState["thread-project-assignments"] ?? {};
51
+ this.database ??= new DatabaseSync(this.stateDatabasePath, { readOnly: true });
52
+ })();
53
+ try {
54
+ await this.refreshPromise;
55
+ } finally {
56
+ this.refreshPromise = null;
57
+ }
58
+ }
59
+
60
+ get(threadId) {
61
+ const thread = this.database
62
+ ?.prepare("SELECT title, name, cwd, archived FROM threads WHERE id = ?")
63
+ .get(threadId);
64
+ if (!thread) return null;
65
+
66
+ const threadTitle = this.threadNames.get(threadId) ?? thread.name ?? thread.title ?? "Untitled task";
67
+ if (this.projectlessThreadIds.has(threadId)) {
68
+ return {
69
+ sourceKind: "chat",
70
+ projectId: null,
71
+ projectName: null,
72
+ threadTitle,
73
+ archived: Boolean(thread.archived),
74
+ };
75
+ }
76
+
77
+ const assignment = this.projectAssignments[threadId];
78
+ const assignedProject = assignment?.projectId ? this.projects.get(assignment.projectId) : null;
79
+ const inferredProject = assignedProject ?? projectContaining([...this.projects.values()], thread.cwd);
80
+ return {
81
+ sourceKind: "project",
82
+ projectId: inferredProject?.id ?? assignment?.projectId ?? null,
83
+ projectName: inferredProject?.name ?? path.basename(thread.cwd),
84
+ threadTitle,
85
+ archived: Boolean(thread.archived),
86
+ };
87
+ }
88
+
89
+ close() {
90
+ this.database?.close();
91
+ this.database = null;
92
+ }
93
+ }