notioncode 0.1.0 → 0.1.2

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 (78) hide show
  1. package/README.md +22 -9
  2. package/agent-runtime-server/package-lock.json +4377 -0
  3. package/agent-runtime-server/package.json +36 -0
  4. package/agent-runtime-server/scripts/fix-node-pty.js +67 -0
  5. package/agent-runtime-server/server/agent-session-service.js +816 -0
  6. package/agent-runtime-server/server/claude-sdk.js +836 -0
  7. package/agent-runtime-server/server/cli.js +330 -0
  8. package/agent-runtime-server/server/constants/config.js +5 -0
  9. package/agent-runtime-server/server/cursor-cli.js +335 -0
  10. package/agent-runtime-server/server/database/db.js +653 -0
  11. package/agent-runtime-server/server/database/init.sql +99 -0
  12. package/agent-runtime-server/server/gemini-cli.js +460 -0
  13. package/agent-runtime-server/server/gemini-response-handler.js +79 -0
  14. package/agent-runtime-server/server/index.js +2569 -0
  15. package/agent-runtime-server/server/load-env.js +32 -0
  16. package/agent-runtime-server/server/middleware/auth.js +132 -0
  17. package/agent-runtime-server/server/openai-codex.js +512 -0
  18. package/agent-runtime-server/server/projects.js +2594 -0
  19. package/agent-runtime-server/server/providers/claude/adapter.js +278 -0
  20. package/agent-runtime-server/server/providers/codex/adapter.js +248 -0
  21. package/agent-runtime-server/server/providers/cursor/adapter.js +353 -0
  22. package/agent-runtime-server/server/providers/gemini/adapter.js +186 -0
  23. package/agent-runtime-server/server/providers/registry.js +44 -0
  24. package/agent-runtime-server/server/providers/types.js +119 -0
  25. package/agent-runtime-server/server/providers/utils.js +29 -0
  26. package/agent-runtime-server/server/routes/agent-sessions.js +238 -0
  27. package/agent-runtime-server/server/routes/agent.js +1244 -0
  28. package/agent-runtime-server/server/routes/auth.js +144 -0
  29. package/agent-runtime-server/server/routes/cli-auth.js +478 -0
  30. package/agent-runtime-server/server/routes/codex.js +329 -0
  31. package/agent-runtime-server/server/routes/commands.js +596 -0
  32. package/agent-runtime-server/server/routes/cursor.js +798 -0
  33. package/agent-runtime-server/server/routes/gemini.js +24 -0
  34. package/agent-runtime-server/server/routes/git.js +1508 -0
  35. package/agent-runtime-server/server/routes/mcp-utils.js +48 -0
  36. package/agent-runtime-server/server/routes/mcp.js +552 -0
  37. package/agent-runtime-server/server/routes/messages.js +61 -0
  38. package/agent-runtime-server/server/routes/plugins.js +307 -0
  39. package/agent-runtime-server/server/routes/projects.js +548 -0
  40. package/agent-runtime-server/server/routes/settings.js +276 -0
  41. package/agent-runtime-server/server/routes/taskmaster.js +1963 -0
  42. package/agent-runtime-server/server/routes/user.js +123 -0
  43. package/agent-runtime-server/server/services/notification-orchestrator.js +227 -0
  44. package/agent-runtime-server/server/services/vapid-keys.js +35 -0
  45. package/agent-runtime-server/server/sessionManager.js +226 -0
  46. package/agent-runtime-server/server/utils/commandParser.js +303 -0
  47. package/agent-runtime-server/server/utils/frontmatter.js +18 -0
  48. package/agent-runtime-server/server/utils/gitConfig.js +34 -0
  49. package/agent-runtime-server/server/utils/mcp-detector.js +198 -0
  50. package/agent-runtime-server/server/utils/plugin-loader.js +457 -0
  51. package/agent-runtime-server/server/utils/plugin-process-manager.js +184 -0
  52. package/agent-runtime-server/server/utils/taskmaster-websocket.js +129 -0
  53. package/agent-runtime-server/shared/modelConstants.js +12 -0
  54. package/agent-runtime-server/shared/modelConstants.test.js +34 -0
  55. package/agent-runtime-server/shared/networkHosts.js +22 -0
  56. package/agent-runtime-server/test_sdk.mjs +16 -0
  57. package/bin/bridges/darwin-x64/nocode-bridge +0 -0
  58. package/bin/{nocode-local.js → notioncode.js} +2 -8
  59. package/dist/assets/icon-CQtd7WEB.png +0 -0
  60. package/dist/assets/index-D_1ZrHDe.js +1 -0
  61. package/dist/assets/index-DhCWie1Z.css +1 -0
  62. package/dist/assets/index-DkGqIiwF.js +689 -0
  63. package/dist/index.html +46 -0
  64. package/dist/onboarding/step1_create.png +0 -0
  65. package/dist/onboarding/step2_capabilities.png +0 -0
  66. package/dist/onboarding/step2b_content_access.png +0 -0
  67. package/dist/onboarding/step2c_page_access.png +0 -0
  68. package/dist/onboarding/step3_token.png +0 -0
  69. package/dist/onboarding/step4_webhook.png +0 -0
  70. package/dist/onboarding/step6a_verify.png +0 -0
  71. package/dist/onboarding/step6b_copy_verify_token.png +0 -0
  72. package/dist/tinyfish-fish-only.png +0 -0
  73. package/lib/certs.js +332 -0
  74. package/lib/install.js +48 -4
  75. package/lib/start.js +346 -29
  76. package/package.json +10 -4
  77. package/src/shared/modelRegistry.d.ts +24 -0
  78. package/src/shared/modelRegistry.js +163 -0
package/lib/start.js CHANGED
@@ -1,14 +1,20 @@
1
+ import { createReadStream, existsSync, readFileSync, statSync } from 'node:fs';
2
+ import http from 'node:http';
3
+ import https from 'node:https';
1
4
  import path from 'node:path';
2
5
  import { spawn, spawnSync } from 'node:child_process';
3
- import { existsSync } from 'node:fs';
4
6
  import { fileURLToPath } from 'node:url';
5
7
  import { setTimeout as sleep } from 'node:timers/promises';
6
8
 
9
+ import defaultBrowserId from 'default-browser-id';
10
+
7
11
  import { commandForPlatform } from './platform.js';
8
12
  import { ensureBridgeBinary } from './install.js';
13
+ import { diagnoseTrustedLocalhostCert, ensureTrustedLocalhostCert } from './certs.js';
9
14
 
10
15
  const BRIDGE_HEALTH_URL = 'http://127.0.0.1:3456/healthz';
11
16
  const LOCAL_UI_URL = 'http://127.0.0.1:1420/';
17
+ const LOCAL_UI_HTTPS_URL = 'https://localhost:1420/';
12
18
  const CLOUD_URL = process.env.NOCODE_CLOUD_URL || 'https://www.notioncode.live';
13
19
 
14
20
  function packageRootDir() {
@@ -20,10 +26,18 @@ function detectRuntimeRoot() {
20
26
  return process.env.NOCODE_RUNTIME_ROOT;
21
27
  }
22
28
 
29
+ const packageRoot = packageRootDir();
30
+ if (
31
+ existsSync(path.join(packageRoot, 'agent-runtime-server', 'package.json')) ||
32
+ existsSync(path.join(packageRoot, 'dist', 'index.html'))
33
+ ) {
34
+ return packageRoot;
35
+ }
36
+
23
37
  const candidates = [
24
- path.resolve(packageRootDir(), '..', '..'),
38
+ path.resolve(packageRoot, '..', '..'),
25
39
  process.cwd(),
26
- path.resolve(packageRootDir(), '..'),
40
+ path.resolve(packageRoot, '..'),
27
41
  ];
28
42
 
29
43
  for (const candidate of candidates) {
@@ -44,10 +58,34 @@ function spawnLogged(cmd, args, options = {}) {
44
58
  });
45
59
  }
46
60
 
61
+ function requestReady(url, options = {}) {
62
+ const target = new URL(url);
63
+ const client = target.protocol === 'https:' ? https : http;
64
+
65
+ return new Promise((resolve, reject) => {
66
+ const request = client.request(
67
+ target,
68
+ {
69
+ method: 'GET',
70
+ ...(target.protocol === 'https:' ? { rejectUnauthorized: options.rejectUnauthorized !== false } : {}),
71
+ },
72
+ (response) => {
73
+ response.resume();
74
+ resolve(response.statusCode >= 200 && response.statusCode < 400);
75
+ }
76
+ );
77
+
78
+ request.on('error', reject);
79
+ request.setTimeout(2_000, () => {
80
+ request.destroy(new Error(`Timed out waiting for ${url}`));
81
+ });
82
+ request.end();
83
+ });
84
+ }
85
+
47
86
  async function isReady(url) {
48
87
  try {
49
- const response = await fetch(url, { method: 'GET' });
50
- return response.ok;
88
+ return await requestReady(url);
51
89
  } catch {
52
90
  return false;
53
91
  }
@@ -58,9 +96,9 @@ async function waitFor(url, timeoutMs, label) {
58
96
  let lastError = null;
59
97
  while (Date.now() < deadline) {
60
98
  try {
61
- const response = await fetch(url, { method: 'GET' });
62
- if (response.ok) return;
63
- lastError = new Error(`${label} returned ${response.status}`);
99
+ const ready = await requestReady(url);
100
+ if (ready) return;
101
+ lastError = new Error(`${label} returned a non-ready response`);
64
102
  } catch (error) {
65
103
  lastError = error;
66
104
  }
@@ -69,6 +107,26 @@ async function waitFor(url, timeoutMs, label) {
69
107
  throw lastError || new Error(`${label} did not become ready.`);
70
108
  }
71
109
 
110
+ async function verifyTrustedBridge(url) {
111
+ const deadline = Date.now() + 10_000;
112
+ let lastError = null;
113
+
114
+ while (Date.now() < deadline) {
115
+ try {
116
+ const ready = await requestReady(url, { rejectUnauthorized: true });
117
+ if (ready) {
118
+ return;
119
+ }
120
+ lastError = new Error(`Trusted bridge check returned a non-ready response for ${url}`);
121
+ } catch (error) {
122
+ lastError = error;
123
+ }
124
+ await sleep(300);
125
+ }
126
+
127
+ throw lastError || new Error(`Timed out verifying trusted bridge at ${url}`);
128
+ }
129
+
72
130
  function openBrowser(url) {
73
131
  if (process.env.NOCODE_NO_OPEN === '1') return;
74
132
  if (process.platform === 'darwin') {
@@ -82,11 +140,169 @@ function openBrowser(url) {
82
140
  spawn(commandForPlatform('xdg-open'), [url], { stdio: 'ignore', detached: true }).unref();
83
141
  }
84
142
 
143
+ function trimOutput(value) {
144
+ return typeof value === 'string' ? value.trim() : '';
145
+ }
146
+
147
+ function detectDefaultBrowser() {
148
+ if (process.env.NOCODE_DEFAULT_BROWSER?.trim()) {
149
+ return process.env.NOCODE_DEFAULT_BROWSER.trim();
150
+ }
151
+
152
+ try {
153
+ if (process.platform === 'darwin') {
154
+ const bundleId = defaultBrowserId();
155
+ if (bundleId) {
156
+ return trimOutput(bundleId);
157
+ }
158
+ }
159
+
160
+ if (process.platform === 'win32') {
161
+ const result = spawnSync(
162
+ 'reg',
163
+ [
164
+ 'query',
165
+ 'HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice',
166
+ '/v',
167
+ 'ProgId',
168
+ ],
169
+ { encoding: 'utf8' }
170
+ );
171
+ if (result.status === 0) {
172
+ return trimOutput(result.stdout);
173
+ }
174
+ }
175
+
176
+ const result = spawnSync('xdg-settings', ['get', 'default-web-browser'], {
177
+ encoding: 'utf8',
178
+ });
179
+ if (result.status === 0) {
180
+ return trimOutput(result.stdout);
181
+ }
182
+ } catch {
183
+ // Ignore detection failures and fall back to HTTP.
184
+ }
185
+
186
+ return '';
187
+ }
188
+
189
+ function isSafariDefaultBrowser(defaultBrowser) {
190
+ const normalized = defaultBrowser.toLowerCase();
191
+ return (
192
+ normalized.includes('com.apple.safari') ||
193
+ normalized.includes('safari.app') ||
194
+ normalized.includes('safari technology preview')
195
+ );
196
+ }
197
+
198
+ function shouldUseHttpsLocalUi() {
199
+ const explicit = process.env.NOCODE_LOCAL_UI_HTTPS?.trim().toLowerCase();
200
+ if (explicit === '1' || explicit === 'true' || explicit === 'https') {
201
+ return true;
202
+ }
203
+ if (explicit === '0' || explicit === 'false' || explicit === 'http') {
204
+ return false;
205
+ }
206
+
207
+ return isSafariDefaultBrowser(detectDefaultBrowser());
208
+ }
209
+
85
210
  function hasLocalUiRepo() {
86
211
  const runtimeRoot = detectRuntimeRoot();
87
212
  return existsSync(path.join(runtimeRoot, 'package.json')) && existsSync(path.join(runtimeRoot, 'node_modules', 'vite', 'bin', 'vite.js'));
88
213
  }
89
214
 
215
+ function hasBundledLocalUi(runtimeRoot) {
216
+ return existsSync(path.join(runtimeRoot, 'dist', 'index.html'));
217
+ }
218
+
219
+ function mimeTypeForPath(filePath) {
220
+ const ext = path.extname(filePath).toLowerCase();
221
+ switch (ext) {
222
+ case '.html':
223
+ return 'text/html; charset=utf-8';
224
+ case '.js':
225
+ return 'text/javascript; charset=utf-8';
226
+ case '.css':
227
+ return 'text/css; charset=utf-8';
228
+ case '.json':
229
+ return 'application/json; charset=utf-8';
230
+ case '.svg':
231
+ return 'image/svg+xml';
232
+ case '.png':
233
+ return 'image/png';
234
+ case '.jpg':
235
+ case '.jpeg':
236
+ return 'image/jpeg';
237
+ case '.webp':
238
+ return 'image/webp';
239
+ case '.woff2':
240
+ return 'font/woff2';
241
+ case '.txt':
242
+ return 'text/plain; charset=utf-8';
243
+ default:
244
+ return 'application/octet-stream';
245
+ }
246
+ }
247
+
248
+ function resolveStaticAssetPath(rootDir, requestUrl) {
249
+ const url = new URL(requestUrl, 'http://localhost');
250
+ const normalizedPath = decodeURIComponent(url.pathname);
251
+ const candidate = normalizedPath === '/'
252
+ ? path.join(rootDir, 'index.html')
253
+ : path.join(rootDir, normalizedPath.replace(/^\/+/, ''));
254
+
255
+ const normalizedRoot = `${path.resolve(rootDir)}${path.sep}`;
256
+ const resolvedCandidate = path.resolve(candidate);
257
+ if (resolvedCandidate !== path.resolve(rootDir, 'index.html') && !resolvedCandidate.startsWith(normalizedRoot)) {
258
+ return null;
259
+ }
260
+
261
+ if (existsSync(resolvedCandidate)) {
262
+ const stats = statSync(resolvedCandidate);
263
+ if (stats.isFile()) {
264
+ return resolvedCandidate;
265
+ }
266
+ }
267
+
268
+ if (!path.extname(normalizedPath)) {
269
+ return path.join(rootDir, 'index.html');
270
+ }
271
+
272
+ return null;
273
+ }
274
+
275
+ function startBundledLocalUiServer({ runtimeRoot, useHttpsLocalUi, localHttps, localUiHost }) {
276
+ const distRoot = path.join(runtimeRoot, 'dist');
277
+ const listener = (req, res) => {
278
+ const targetFile = resolveStaticAssetPath(distRoot, req.url || '/');
279
+ if (!targetFile || !existsSync(targetFile)) {
280
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
281
+ res.end('Not found');
282
+ return;
283
+ }
284
+
285
+ res.writeHead(200, {
286
+ 'Content-Type': mimeTypeForPath(targetFile),
287
+ 'Cache-Control': targetFile.endsWith('index.html') ? 'no-cache' : 'public, max-age=31536000, immutable',
288
+ });
289
+ createReadStream(targetFile).pipe(res);
290
+ };
291
+
292
+ const server = useHttpsLocalUi
293
+ ? https.createServer(
294
+ {
295
+ cert: readFileSync(localHttps.certFile),
296
+ key: readFileSync(localHttps.keyFile),
297
+ },
298
+ listener
299
+ )
300
+ : http.createServer(listener);
301
+
302
+ server.listen(1420, localUiHost);
303
+ return server;
304
+ }
305
+
90
306
  function hasCargoBridgeSource(runtimeRoot) {
91
307
  return existsSync(path.join(runtimeRoot, 'src-tauri', 'Cargo.toml'));
92
308
  }
@@ -98,15 +314,19 @@ function canUseCargo() {
98
314
  return result.status === 0;
99
315
  }
100
316
 
101
- function ensureSidecarInstallIfRepoPresent() {
102
- const runtimeRoot = detectRuntimeRoot();
103
- if (!existsSync(path.join(runtimeRoot, 'agent-runtime-server', 'package.json'))) {
317
+ function ensureSidecarInstallIfPresent(runtimeRoot) {
318
+ const sidecarRoot = path.join(runtimeRoot, 'agent-runtime-server');
319
+ if (!existsSync(path.join(sidecarRoot, 'package.json'))) {
320
+ return;
321
+ }
322
+
323
+ if (existsSync(path.join(sidecarRoot, 'node_modules', '.package-lock.json'))) {
104
324
  return;
105
325
  }
106
326
 
107
327
  const npmCmd = commandForPlatform('npm');
108
- const result = spawnSync(npmCmd, ['--prefix', 'agent-runtime-server', 'install'], {
109
- cwd: runtimeRoot,
328
+ const result = spawnSync(npmCmd, ['install'], {
329
+ cwd: sidecarRoot,
110
330
  stdio: 'inherit',
111
331
  });
112
332
  if (result.status !== 0) {
@@ -114,32 +334,77 @@ function ensureSidecarInstallIfRepoPresent() {
114
334
  }
115
335
  }
116
336
 
337
+ function hasBundledSidecar(runtimeRoot) {
338
+ return existsSync(path.join(runtimeRoot, 'agent-runtime-server', 'server', 'index.js'));
339
+ }
340
+
341
+ function ensureSidecarInstallIfRepoPresent() {
342
+ const runtimeRoot = detectRuntimeRoot();
343
+ if (!existsSync(path.join(runtimeRoot, 'agent-runtime-server', 'package.json'))) {
344
+ return;
345
+ }
346
+ ensureSidecarInstallIfPresent(runtimeRoot);
347
+ }
348
+
117
349
  export async function startLocalCompanion(options = {}) {
118
350
  const runtimeRoot = detectRuntimeRoot();
119
351
  const requestedLocalUi = options.withLocalUi !== false;
120
- const shouldStartLocalUi = requestedLocalUi && hasLocalUiRepo();
352
+ const shouldStartRepoLocalUi = requestedLocalUi && hasLocalUiRepo();
353
+ const shouldStartBundledLocalUi = requestedLocalUi && !shouldStartRepoLocalUi && hasBundledLocalUi(runtimeRoot);
354
+ const shouldStartLocalUi = shouldStartRepoLocalUi || shouldStartBundledLocalUi;
121
355
  const useCargoBridge = hasCargoBridgeSource(runtimeRoot) && canUseCargo();
122
- const bridgeExecutable = useCargoBridge ? null : await ensureBridgeBinary(options.version);
356
+ const useHttpsLocalUi =
357
+ typeof options.localUiHttps === 'boolean' ? options.localUiHttps : shouldUseHttpsLocalUi();
358
+ const localUiUrl = useHttpsLocalUi ? LOCAL_UI_HTTPS_URL : LOCAL_UI_URL;
359
+ const localUiHost = useHttpsLocalUi ? 'localhost' : '127.0.0.1';
360
+ let localHttps = null;
361
+ let bundledLocalUiServer = null;
123
362
 
124
- ensureSidecarInstallIfRepoPresent();
363
+ if (shouldStartLocalUi && useHttpsLocalUi) {
364
+ localHttps = await ensureTrustedLocalhostCert();
365
+ }
366
+
367
+ if (hasBundledSidecar(runtimeRoot)) {
368
+ ensureSidecarInstallIfPresent(runtimeRoot);
369
+ } else {
370
+ ensureSidecarInstallIfRepoPresent();
371
+ }
125
372
 
126
373
  let vite = null;
127
374
  if (shouldStartLocalUi) {
128
- if (await isReady(LOCAL_UI_URL)) {
129
- console.log('[notioncode] Reusing existing local UI on http://127.0.0.1:1420 .');
375
+ if (await isReady(localUiUrl)) {
376
+ console.log(`[notioncode] Reusing existing local UI on ${localUiUrl} .`);
130
377
  } else {
131
- console.log('[notioncode] Starting local UI on http://127.0.0.1:1420 ...');
132
- vite = spawnLogged(commandForPlatform('node'), ['node_modules/vite/bin/vite.js', '--host', '127.0.0.1'], {
133
- cwd: runtimeRoot,
134
- env: {
135
- VITE_SINGLE_USER_MODE: 'true',
136
- },
137
- });
378
+ console.log(`[notioncode] Starting local UI on ${localUiUrl} ...`);
379
+ if (shouldStartRepoLocalUi) {
380
+ vite = spawnLogged(commandForPlatform('node'), ['node_modules/vite/bin/vite.js', '--host', localUiHost], {
381
+ cwd: runtimeRoot,
382
+ env: {
383
+ VITE_SINGLE_USER_MODE: 'true',
384
+ NOCODE_LOCAL_UI_USE_HTTPS: useHttpsLocalUi ? '1' : '0',
385
+ NOCODE_LOCAL_UI_HOST: localUiHost,
386
+ ...(localHttps
387
+ ? {
388
+ NOCODE_LOCAL_UI_CERT_FILE: localHttps.certFile,
389
+ NOCODE_LOCAL_UI_KEY_FILE: localHttps.keyFile,
390
+ }
391
+ : {}),
392
+ },
393
+ });
394
+ } else if (shouldStartBundledLocalUi) {
395
+ bundledLocalUiServer = startBundledLocalUiServer({
396
+ runtimeRoot,
397
+ useHttpsLocalUi,
398
+ localHttps,
399
+ localUiHost,
400
+ });
401
+ }
138
402
  }
139
403
  }
140
404
 
141
405
  let bridge = null;
142
- if (await isReady(BRIDGE_HEALTH_URL)) {
406
+ const bridgeAlreadyReady = await isReady(BRIDGE_HEALTH_URL);
407
+ if (bridgeAlreadyReady) {
143
408
  console.log('[notioncode] Reusing existing bridge on http://127.0.0.1:3456 .');
144
409
  } else {
145
410
  console.log('[notioncode] Starting local bridge on http://127.0.0.1:3456 ...');
@@ -150,6 +415,7 @@ export async function startLocalCompanion(options = {}) {
150
415
  { cwd: runtimeRoot }
151
416
  );
152
417
  } else {
418
+ const bridgeExecutable = await ensureBridgeBinary(options.version);
153
419
  bridge = spawnLogged(bridgeExecutable, [], {
154
420
  cwd: runtimeRoot,
155
421
  env: {
@@ -162,6 +428,7 @@ export async function startLocalCompanion(options = {}) {
162
428
  const shutdown = (code = 0) => {
163
429
  vite?.kill('SIGTERM');
164
430
  bridge?.kill('SIGTERM');
431
+ bundledLocalUiServer?.close();
165
432
  setTimeout(() => process.exit(code), 250);
166
433
  };
167
434
 
@@ -180,15 +447,65 @@ export async function startLocalCompanion(options = {}) {
180
447
  const bridgeTimeoutMs = useCargoBridge ? 180_000 : 15_000;
181
448
  await waitFor(BRIDGE_HEALTH_URL, bridgeTimeoutMs, 'Bridge');
182
449
  if (shouldStartLocalUi) {
183
- await waitFor(LOCAL_UI_URL, 15_000, 'Local UI');
450
+ await waitFor(localUiUrl, 15_000, 'Local UI');
451
+ if (useHttpsLocalUi) {
452
+ try {
453
+ await verifyTrustedBridge(localUiUrl);
454
+ } catch (error) {
455
+ throw new Error(
456
+ `Safari-compatible local bridge could not be initialized.\n\nReason:\n ${error instanceof Error ? error.message : String(error)}\n\nFix:\n Install mkcert and trust the local certificate authority, then rerun:\n mkcert -install\n npx notioncode start`
457
+ );
458
+ }
459
+ }
184
460
  }
185
461
 
186
462
  console.log('[notioncode] Ready.');
187
463
  console.log(`[notioncode] Cloud UI: ${CLOUD_URL}`);
188
464
  if (shouldStartLocalUi) {
189
- console.log(`[notioncode] Local UI: ${LOCAL_UI_URL}`);
465
+ console.log(`[notioncode] Local UI: ${localUiUrl}`);
190
466
  }
191
- const entryUrl = shouldStartLocalUi ? LOCAL_UI_URL : CLOUD_URL;
467
+ const entryUrl = CLOUD_URL;
192
468
  console.log(`[notioncode] Opening: ${entryUrl}`);
193
469
  openBrowser(entryUrl);
194
470
  }
471
+
472
+ export async function runDoctor() {
473
+ const certs = diagnoseTrustedLocalhostCert();
474
+ const defaultBrowser = detectDefaultBrowser();
475
+ const useHttpsLocalUi = shouldUseHttpsLocalUi();
476
+ const checks = [
477
+ ['node', process.version],
478
+ ['platform', `${process.platform}/${process.arch}`],
479
+ ['default browser', defaultBrowser || 'unknown'],
480
+ ['local UI scheme', useHttpsLocalUi ? 'https' : 'http'],
481
+ ['mkcert', certs.mkcertInstalled ? 'installed' : 'missing'],
482
+ ['localhost cert', certs.certExists ? certs.certFile : 'missing'],
483
+ ['localhost key', certs.keyExists ? certs.keyFile : 'missing'],
484
+ ];
485
+
486
+ for (const [label, detail] of checks) {
487
+ console.log(`[OK] ${label}: ${detail}`);
488
+ }
489
+
490
+ const bridgeReady = await isReady(BRIDGE_HEALTH_URL);
491
+ console.log(`[${bridgeReady ? 'OK' : 'WARN'}] bridge healthz: ${bridgeReady ? BRIDGE_HEALTH_URL : 'not reachable'}`);
492
+
493
+ const selectedLocalUiUrl = useHttpsLocalUi ? LOCAL_UI_HTTPS_URL : LOCAL_UI_URL;
494
+ const localUiReachable = await isReady(selectedLocalUiUrl);
495
+ console.log(
496
+ `[${localUiReachable ? 'OK' : 'WARN'}] selected local UI: ${
497
+ localUiReachable ? selectedLocalUiUrl : 'not reachable'
498
+ }`
499
+ );
500
+
501
+ if (useHttpsLocalUi && localUiReachable) {
502
+ try {
503
+ await verifyTrustedBridge(LOCAL_UI_HTTPS_URL);
504
+ console.log(`[OK] trusted TLS: ${LOCAL_UI_HTTPS_URL}`);
505
+ } catch (error) {
506
+ console.log(
507
+ `[WARN] trusted TLS: ${error instanceof Error ? error.message : String(error)}`
508
+ );
509
+ }
510
+ }
511
+ }
package/package.json CHANGED
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "name": "notioncode",
3
- "version": "0.1.0",
4
- "description": "Run the Notion Code local companion bridge and open the cloud UI.",
3
+ "version": "0.1.2",
4
+ "description": "Local companion runtime used by the `npx notioncode start` setup flow.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "dependencies": {
8
+ "default-browser-id": "^5.0.1"
9
+ },
7
10
  "bin": {
8
- "notioncode": "bin/nocode-local.js"
11
+ "notioncode": "bin/notioncode.js"
9
12
  },
10
13
  "files": [
14
+ "agent-runtime-server",
11
15
  "bin",
16
+ "dist",
12
17
  "lib",
13
- "README.md"
18
+ "README.md",
19
+ "src"
14
20
  ],
15
21
  "publishConfig": {
16
22
  "access": "public"
@@ -0,0 +1,24 @@
1
+ export type AgentProvider = 'claude' | 'cursor' | 'codex' | 'gemini';
2
+
3
+ export interface ModelOption {
4
+ value: string;
5
+ label: string;
6
+ }
7
+
8
+ export interface ModelConfig {
9
+ OPTIONS: ModelOption[];
10
+ DEFAULT: string;
11
+ }
12
+
13
+ export const CLAUDE_MODELS: ModelConfig;
14
+ export const CURSOR_MODELS: ModelConfig;
15
+ export const CODEX_MODELS: ModelConfig;
16
+ export const GEMINI_MODELS: ModelConfig;
17
+
18
+ export const MODEL_CONFIGS: Record<AgentProvider, ModelConfig>;
19
+
20
+ export function getModelOptions(provider: AgentProvider): ModelOption[];
21
+ export function getDefaultModel(provider: AgentProvider): string;
22
+ export function isSupportedModel(provider: AgentProvider, value: string | null | undefined): boolean;
23
+ export function migrateLegacyModel(provider: AgentProvider, value: string | null | undefined): string | null;
24
+ export function normalizeModel(provider: AgentProvider, value: string | null | undefined): string;