@notis_ai/cli 0.2.0-beta.178.1 → 0.2.0-beta.1792022492092.1

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 (124) hide show
  1. package/README.md +548 -98
  2. package/config/portal-route-roots.json +47 -0
  3. package/dist/agent-hooks/notis-agent-hook.mjs +32901 -5934
  4. package/dist/base-skills/notis-apps/SKILL.md +76 -51
  5. package/dist/base-skills/notis-apps/references/architecture.md +116 -164
  6. package/dist/base-skills/notis-apps/references/design.md +26 -107
  7. package/dist/base-skills/notis-apps/references/memory.md +58 -0
  8. package/dist/base-skills/notis-apps/references/reading.md +52 -74
  9. package/dist/base-skills/notis-apps/references/release.md +105 -96
  10. package/dist/base-skills/notis-apps/references/sdk.md +115 -60
  11. package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
  12. package/dist/base-skills/notis-apps/references/views.md +145 -0
  13. package/dist/base-skills/notis-cli/SKILL.md +29 -12
  14. package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
  15. package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
  16. package/dist/base-skills/notis-cli/references/native-databases.md +6 -5
  17. package/dist/base-skills/notis-cli/references/tool-examples.md +4 -4
  18. package/dist/base-skills/notis-query/SKILL.md +34 -60
  19. package/dist/base-skills/notis-query/references/database-discovery.md +60 -59
  20. package/dist/base-skills/notis-query/references/documents.md +53 -39
  21. package/dist/base-skills/notis-query/references/query.md +57 -524
  22. package/dist/sdk/src/bundleModule.ts +79 -0
  23. package/dist/sdk/src/components/DocumentEditor.tsx +6 -3
  24. package/dist/sdk/src/components/DocumentPage.tsx +144 -0
  25. package/dist/sdk/src/components/RecordComponents.tsx +33 -0
  26. package/dist/sdk/src/components/RenderChartData.tsx +19 -0
  27. package/dist/sdk/src/config.ts +4 -26
  28. package/dist/sdk/src/documents.ts +2 -0
  29. package/dist/sdk/src/hooks/useAction.ts +28 -0
  30. package/dist/sdk/src/hooks/useBackend.ts +3 -2
  31. package/dist/sdk/src/hooks/useCloudComputer.ts +35 -4
  32. package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
  33. package/dist/sdk/src/hooks/useCollectionInteractions.ts +4 -2
  34. package/dist/sdk/src/hooks/useHandover.ts +7 -7
  35. package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
  36. package/dist/sdk/src/hooks/useNativeDocuments.ts +83 -0
  37. package/dist/sdk/src/hooks/useNotis.ts +3 -0
  38. package/dist/sdk/src/hooks/useNotisNavigation.ts +26 -11
  39. package/dist/sdk/src/hooks/useQuery.ts +7 -4
  40. package/dist/sdk/src/hooks/useViewParams.ts +121 -0
  41. package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
  42. package/dist/sdk/src/index.ts +36 -0
  43. package/dist/sdk/src/interactions/shortcuts.tsx +42 -5
  44. package/dist/sdk/src/nativeData.ts +39 -0
  45. package/dist/sdk/src/presentation.tsx +82 -0
  46. package/dist/sdk/src/provider.tsx +9 -4
  47. package/dist/sdk/src/queryCache.ts +5 -3
  48. package/dist/sdk/src/queryErrors.ts +51 -0
  49. package/dist/sdk/src/runtime.ts +163 -13
  50. package/dist/sdk/src/space.ts +425 -0
  51. package/dist/sdk/src/styles.css +96 -72
  52. package/dist/sdk/src/vite.ts +11 -1
  53. package/dist/skill-sync/index.js +571 -80
  54. package/dist/skill-sync/index.js.map +4 -4
  55. package/dist/skill-sync-worker.mjs +858 -233
  56. package/dist/space-harness/host.js +66 -0
  57. package/dist/view-renderer/dist/host.css +1 -0
  58. package/dist/view-renderer/dist/host.js +1028 -0
  59. package/dist/view-renderer/dist/theme.css +73 -0
  60. package/dist/view-renderer/src/dom.js +180 -0
  61. package/dist/view-renderer/src/errors.js +8 -0
  62. package/dist/view-renderer/src/host.tsx +175 -0
  63. package/dist/view-renderer/src/index.js +4 -0
  64. package/dist/view-renderer/src/pool.js +76 -0
  65. package/dist/view-renderer/src/remote-broker.js +59 -0
  66. package/dist/view-renderer/src/renderer.js +201 -0
  67. package/package.json +16 -5
  68. package/skills/notis-apps/cli.md +323 -163
  69. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +3 -3
  70. package/skills/notis-onboarding/BRIEF.md +7 -3
  71. package/src/command-specs/apps.js +2 -1
  72. package/src/command-specs/auth.js +2 -2
  73. package/src/command-specs/files.js +29 -0
  74. package/src/command-specs/handover.js +331 -79
  75. package/src/command-specs/helpers.js +2 -0
  76. package/src/command-specs/index.js +11 -5
  77. package/src/command-specs/profile.js +42 -1
  78. package/src/command-specs/skills.js +242 -1
  79. package/src/command-specs/spaces.js +645 -0
  80. package/src/command-specs/tools.js +0 -6
  81. package/src/command-specs/views.js +69 -0
  82. package/src/runtime/agent-browser.js +92 -29
  83. package/src/runtime/app-boundary-validator.js +7 -4
  84. package/src/runtime/app-data-literals.js +82 -0
  85. package/src/runtime/app-platform.js +321 -253
  86. package/src/runtime/computer-identity.d.ts +30 -0
  87. package/src/runtime/computer-identity.js +151 -0
  88. package/src/runtime/config-lock.js +135 -0
  89. package/src/runtime/confined-space-build.js +74 -0
  90. package/src/runtime/confined-space-verify.js +49 -0
  91. package/src/runtime/delegated-context.js +5 -21
  92. package/src/runtime/native-record-files.js +47 -0
  93. package/src/runtime/oauth.js +18 -1
  94. package/src/runtime/profiles.js +5 -107
  95. package/src/runtime/retired-sdk-files.js +20 -0
  96. package/src/runtime/screenshot-framing.js +85 -0
  97. package/src/runtime/skill-sync/cloud-client.ts +26 -4
  98. package/src/runtime/skill-sync/index.ts +230 -35
  99. package/src/runtime/skill-sync/local-scanner.ts +188 -29
  100. package/src/runtime/skill-sync/native-identity.ts +102 -0
  101. package/src/runtime/skill-sync/native-plan.ts +70 -0
  102. package/src/runtime/skill-sync/sync-plan.ts +16 -9
  103. package/src/runtime/skill-sync/types.ts +32 -6
  104. package/src/runtime/source-root.js +32 -0
  105. package/src/runtime/space-collection-scaffold.js +66 -0
  106. package/src/runtime/space-database-structures.js +229 -0
  107. package/src/runtime/space-fixture-broker.js +50 -0
  108. package/src/runtime/space-lock.js +337 -0
  109. package/src/runtime/space-platform.js +105 -0
  110. package/src/runtime/space-source.js +281 -0
  111. package/src/runtime/space-test-server.js +327 -0
  112. package/src/runtime/space-verification.js +89 -0
  113. package/src/runtime/space-verify-browser-worker.js +51 -0
  114. package/src/runtime/space-view-manifest.js +254 -0
  115. package/src/runtime/store-screenshot.js +10 -7
  116. package/src/runtime/sync-skills.d.ts +3 -1
  117. package/src/runtime/sync-skills.js +10 -2
  118. package/src/runtime/transport.js +8 -6
  119. package/src/runtime/view-render.js +49 -0
  120. package/src/skill-sync-worker.js +14 -2
  121. package/src/space-harness-host.ts +103 -0
  122. package/template/.harness/index.html.tmpl +7 -10
  123. package/template/app/globals.css +1 -0
  124. package/src/command-specs/reports.js +0 -97
@@ -1,16 +1,14 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import {
3
2
  existsSync,
4
3
  mkdirSync,
5
4
  readFileSync,
6
5
  renameSync,
7
- rmSync,
8
- statSync,
9
6
  writeFileSync,
10
7
  } from 'node:fs';
11
8
  import { homedir } from 'node:os';
12
9
  import { dirname, join, parse, resolve } from 'node:path';
13
10
  import { CliError, EXIT_CODES } from './errors.js';
11
+ import { withConfigFileWriteLock } from './config-lock.js';
14
12
  import { getAuthRecovery, quoteShellArgument } from './auth-recovery.js';
15
13
  import { channelFromProfile, cliCommandForChannel, isReleaseChannel } from './channel.js';
16
14
 
@@ -34,14 +32,6 @@ const WORKTREE_RUNTIME_FILENAME = join('.context', 'notis-runtime.json');
34
32
  const WORKTREE_ROUTING_FILENAME = join('.context', 'notis-routing.json');
35
33
  const LOCAL_API_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
36
34
  const LIVE_API_HOSTS = new Set(['api.notis.ai', 'api-beta.notis.ai']);
37
- // Cross-process write lock over ~/.notis/config.json. Independent `notis`
38
- // processes (a `login` in one terminal and a `tools exec` in another) all
39
- // rewrite this file, so the lock directory name and these three timings are the
40
- // whole coordination protocol. Guarded by the concurrency test in
41
- // packages/cli/test/runtime-auth.test.js.
42
- const CONFIG_WRITE_LOCK_TIMEOUT_MS = 5_000;
43
- const CONFIG_WRITE_LOCK_STALE_MS = 2_000;
44
- const CONFIG_WRITE_LOCK_POLL_MS = 10;
45
35
 
46
36
  function clone(value) {
47
37
  return JSON.parse(JSON.stringify(value));
@@ -372,103 +362,11 @@ function writeRawConfig(configFile, config) {
372
362
  renameSync(temporaryFile, configFile);
373
363
  }
374
364
 
375
- // The lock records who holds it and when they took it. Reclaiming a stale lock
376
- // by mtime alone is not safe: the reclaimer cannot tell an abandoned lock from
377
- // one it is about to steal from a live writer, and the victim's own release
378
- // would then delete the thief's lock and admit a third writer. Both sides are
379
- // on the same host, so the stored timestamp and Date.now() share a clock.
380
- function readLockOwner(lockDirectory) {
381
- try {
382
- return JSON.parse(readFileSync(join(lockDirectory, 'owner'), 'utf-8'));
383
- } catch {
384
- return null;
385
- }
386
- }
387
-
388
- function lockDirectoryMtime(lockDirectory) {
389
- try {
390
- return statSync(lockDirectory).mtimeMs;
391
- } catch {
392
- return null;
393
- }
394
- }
395
-
396
- function publishConfigWriteLock(lockDirectory, ownerId) {
397
- const candidateDirectory = `${lockDirectory}.candidate-${ownerId}`;
398
- try {
399
- mkdirSync(candidateDirectory);
400
- writeFileSync(
401
- join(candidateDirectory, 'owner'),
402
- JSON.stringify({ id: ownerId, at: Date.now() }),
403
- { mode: 0o600 },
404
- );
405
- try {
406
- renameSync(candidateDirectory, lockDirectory);
407
- return true;
408
- } catch (error) {
409
- if (!existsSync(lockDirectory)) throw error;
410
- return false;
411
- }
412
- } finally {
413
- rmSync(candidateDirectory, { recursive: true, force: true });
414
- }
415
- }
416
-
365
+ // Independent `notis` processes and Desktop all rewrite this file. The lock
366
+ // protocol lives in config-lock.js, shared with computer.json and mirrored by
367
+ // Desktop.
417
368
  function withConfigWriteLock(callback) {
418
- const configFile = resolveConfigFile();
419
- const lockDirectory = `${configFile}.write-lock`;
420
- const ownerId = `${process.pid}.${randomUUID()}`;
421
- const deadline = Date.now() + CONFIG_WRITE_LOCK_TIMEOUT_MS;
422
- mkdirSync(dirname(configFile), { recursive: true });
423
- for (;;) {
424
- if (!existsSync(lockDirectory) && publishConfigWriteLock(lockDirectory, ownerId)) {
425
- break;
426
- }
427
-
428
- const owner = readLockOwner(lockDirectory);
429
- const observedMtime = lockDirectoryMtime(lockDirectory);
430
- const ownerIsStale =
431
- owner?.id && Date.now() - Number(owner.at) > CONFIG_WRITE_LOCK_STALE_MS;
432
- const ownerlessIsStale =
433
- !owner
434
- && observedMtime !== null
435
- && Date.now() - observedMtime > CONFIG_WRITE_LOCK_STALE_MS;
436
- if (ownerIsStale || ownerlessIsStale) {
437
- try {
438
- const currentOwner = readLockOwner(lockDirectory);
439
- const ownerUnchanged = owner?.id
440
- ? currentOwner?.id === owner.id
441
- : !currentOwner && lockDirectoryMtime(lockDirectory) === observedMtime;
442
- if (ownerUnchanged) {
443
- rmSync(lockDirectory, { recursive: true, force: true });
444
- continue;
445
- }
446
- } catch {
447
- // Losing the reclaim race is normal; fall through and keep waiting.
448
- }
449
- }
450
- if (Date.now() >= deadline) {
451
- throw new Error(`Timed out waiting to update ${configFile}`);
452
- }
453
- Atomics.wait(
454
- new Int32Array(new SharedArrayBuffer(4)),
455
- 0,
456
- 0,
457
- CONFIG_WRITE_LOCK_POLL_MS,
458
- );
459
- }
460
- try {
461
- return callback(configFile);
462
- } finally {
463
- try {
464
- // Never release a lock that was reclaimed from us while we worked.
465
- if (readLockOwner(lockDirectory)?.id === ownerId) {
466
- rmSync(lockDirectory, { recursive: true, force: true });
467
- }
468
- } catch {
469
- // The lock may already have been removed after an interrupted write.
470
- }
471
- }
369
+ return withConfigFileWriteLock(resolveConfigFile(), callback);
472
370
  }
473
371
 
474
372
  export function saveConfig(config) {
@@ -0,0 +1,20 @@
1
+ /** Exact canonical files retired by the one-Shadow-runtime cutover.
2
+ * Modified or additional source is retained for explicit reconciliation. */
3
+ // Host mounting code stays in the canonical SDK and CLI host bundle, not in
4
+ // authored apps' public SDK mirror. It is not a public package export.
5
+ export const HOST_ONLY_SDK_FILES = Object.freeze(['src/presentation.tsx']);
6
+
7
+ export const RETIRED_SDK_FILES = Object.freeze({
8
+ "src/presentation.tsx": [
9
+ "d29022f92e1e96b451b62cc8a561878a1c0cf8a3b4c0419181de7266f8a1b525"
10
+ ],
11
+ "src/isolatedHost.tsx": [
12
+ "7f59049281a9533d39c557ca75635073c2f404f1eb0a528b274323d61db776cd"
13
+ ],
14
+ "src/frameProtocol.ts": [
15
+ "f32337ddb507718be04289a4acf50cf2485698bc716556230f35f06a6f633f19"
16
+ ],
17
+ "src/frameIsolation.ts": [
18
+ "8b57cb38f618eefc4133ae50fbf2b37bbdef5be02e44b39758d8328fb2a59ab6"
19
+ ]
20
+ });
@@ -0,0 +1,85 @@
1
+ /** Runs only in the capture harness, never in an installed app. */
2
+ export function measureScreenshotContent() {
3
+ const root = document.getElementById('root');
4
+ if (!root) return null;
5
+ const viewportWidth = document.documentElement.clientWidth;
6
+ const visible = (element) => {
7
+ const rect = element.getBoundingClientRect();
8
+ const style = getComputedStyle(element);
9
+ return rect.width > 0 && rect.height > 0
10
+ && style.display !== 'none' && style.visibility !== 'hidden';
11
+ };
12
+
13
+ // Follow layout wrappers only while they contain the entire visible app.
14
+ // Never select one column, card, or control out of a multi-pane layout.
15
+ let node = root;
16
+ let content = null;
17
+ while (node) {
18
+ const hasOwnText = Array.from(node.childNodes).some(
19
+ (child) => child.nodeType === 3 && child.textContent.trim(),
20
+ );
21
+ const children = Array.from(node.children).filter(visible);
22
+ if (hasOwnText || children.length !== 1) break;
23
+ node = children[0];
24
+ const rect = node.getBoundingClientRect();
25
+ if (['DIV', 'MAIN', 'SECTION', 'ARTICLE', 'FORM'].includes(node.tagName)
26
+ && rect.width >= 320 && rect.height >= 160
27
+ && rect.width < viewportWidth * 0.9) {
28
+ content = node;
29
+ }
30
+ }
31
+ if (!content) return null;
32
+ content.setAttribute('data-notis-screenshot-content', '');
33
+ const rect = content.getBoundingClientRect();
34
+ return {
35
+ selector: '[data-notis-screenshot-content]',
36
+ width: rect.width,
37
+ height: rect.height,
38
+ viewport_width: viewportWidth,
39
+ };
40
+ }
41
+
42
+ export const MEASURE_SCREENSHOT_CONTENT_SCRIPT = `(${measureScreenshotContent.toString()})()`;
43
+
44
+ /** Keep long pages readable, and rasterize bounded views at their final width. */
45
+ export function contentCapturePlan(bounds, viewport, outputWidth, outputHeight) {
46
+ const aspect = outputWidth / outputHeight;
47
+ if (bounds.width / bounds.height < aspect * 0.75) {
48
+ const width = Math.max(768, Math.ceil((bounds.width + 32) / 8) * 8);
49
+ return { focus: false, width, height: Math.round(width / aspect), scale: outputWidth / width };
50
+ }
51
+ return {
52
+ focus: true,
53
+ width: viewport.width,
54
+ height: viewport.height,
55
+ scale: Math.min(4, outputWidth / bounds.width),
56
+ };
57
+ }
58
+
59
+ export function safeViewportHeight(rectangles, height) {
60
+ const crossing = rectangles.filter((rect) => rect.top < height && rect.bottom > height
61
+ && rect.top >= height * 0.85);
62
+ return crossing.length
63
+ ? Math.max(Math.ceil(height * 0.85), Math.floor(Math.min(...crossing.map((rect) => rect.top)) - 8))
64
+ : height;
65
+ }
66
+
67
+ // End a long-page viewport in whitespace rather than through a text line or
68
+ // control. Only geometry leaves the browser; no page text is returned.
69
+ export const SAFE_VIEWPORT_HEIGHT_SCRIPT = `(() => {
70
+ const root = document.getElementById('root');
71
+ if (!root) return innerHeight;
72
+ const rects = [];
73
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
74
+ let node;
75
+ while ((node = walker.nextNode())) {
76
+ if (!node.textContent.trim() || getComputedStyle(node.parentElement).visibility !== 'visible') continue;
77
+ const range = document.createRange(); range.selectNodeContents(node);
78
+ for (const rect of range.getClientRects()) rects.push({ top: rect.top, bottom: rect.bottom });
79
+ }
80
+ for (const control of root.querySelectorAll('button,input,select,textarea')) {
81
+ const rect = control.getBoundingClientRect();
82
+ rects.push({ top: rect.top, bottom: rect.bottom });
83
+ }
84
+ return (${safeViewportHeight.toString()})(rects, innerHeight);
85
+ })()`;
@@ -1,5 +1,7 @@
1
- import { createSkillBundleBase64 } from './local-scanner';
2
- import type { AgentTargets, LocalSkill, SyncPullResponse, SyncPushResponse, SyncSettings } from './types';
1
+ import { captureNativeSkillFiles, createSkillBundleBase64 } from './local-scanner';
2
+ import { createHash } from 'node:crypto';
3
+ import { checkedNativeReference } from './native-identity';
4
+ import type { AgentTargets, LocalSkill, NativeSkillReference, SyncPullResponse, SyncPushResponse, SyncSettings } from './types';
3
5
 
4
6
  type JsonBody = Record<string, unknown> | undefined;
5
7
 
@@ -34,7 +36,7 @@ export async function fetchSyncSettings(serverUrl: string, jwt: string): Promise
34
36
 
35
37
  export async function pullSkills(serverUrl: string, jwt: string): Promise<SyncPullResponse> {
36
38
  return requestJson<SyncPullResponse>(`${serverUrl}/portal_skills/sync-pull`, jwt, {
37
- body: {},
39
+ body: { skill_sync_protocol: 2 },
38
40
  });
39
41
  }
40
42
 
@@ -49,11 +51,16 @@ export async function pushChangedSkills(
49
51
  skill_md: skill.skillMd,
50
52
  source_url: skill.sourceUrl,
51
53
  folder_hash: skill.folderHash,
52
- bundle_base64: await createSkillBundleBase64(skill),
54
+ ...(skill.nativeReference ? { bundle_files: await captureNativeSkillFiles(skill) }
55
+ : { bundle_base64: await createSkillBundleBase64(skill) }),
56
+ ...(skill.cloudId ? { cloud_id: skill.cloudId } : {}),
57
+ ...(skill.nativeReference ? { native_reference: skill.nativeReference, display_name: skill.displayName,
58
+ native_scope: skill.nativeScope } : {}),
53
59
  })));
54
60
 
55
61
  return requestJson<SyncPushResponse>(`${serverUrl}/portal_skills/sync-push`, jwt, {
56
62
  body: {
63
+ skill_sync_protocol: 2,
57
64
  skills: payloadSkills,
58
65
  },
59
66
  });
@@ -75,7 +82,22 @@ export async function updateAgentTargets(
75
82
  skillId: string,
76
83
  targets: Partial<AgentTargets>,
77
84
  expectedUpdatedAt?: string,
85
+ native?: { reference: NativeSkillReference; settingsRevision: number },
78
86
  ): Promise<{ success: boolean; agent_targets: AgentTargets; updated_at?: string }> {
87
+ if (native) {
88
+ checkedNativeReference(native.reference, skillId);
89
+ if (!Number.isSafeInteger(native.settingsRevision) || native.settingsRevision < 0) throw new Error('Refresh native Skill settings before changing assignments.');
90
+ const payload = { operation: 'update', target: { skill_id: skillId, access_revision: native.reference.access_revision },
91
+ revision: native.settingsRevision, patch: { agent_targets: Object.fromEntries(Object.entries(targets).sort(([a], [b]) => a.localeCompare(b))) } };
92
+ const requestId = 'sync-settings-' + createHash('sha256').update(JSON.stringify(payload)).digest('hex');
93
+ const saved = await requestJson<{ receipt_id: string; skill_id: string; target: { skill_id: string }; settings: { agent_targets: AgentTargets; updated_at: string };
94
+ current?: { target: { skill_id: string }; settings: { agent_targets: AgentTargets; updated_at: string } } }>(`${serverUrl}/portal_skills/native-settings`, jwt,
95
+ { body: { ...payload, request_id: requestId } });
96
+ const current = saved.current || saved;
97
+ if (saved.skill_id !== skillId || typeof saved.receipt_id !== 'string' || !saved.receipt_id
98
+ || current.target?.skill_id !== skillId || !current.settings) throw new Error('Native assignment update was not confirmed.');
99
+ return { success: true, agent_targets: current.settings.agent_targets, updated_at: current.settings.updated_at };
100
+ }
79
101
  return requestJson<{ success: boolean; agent_targets: AgentTargets; updated_at?: string }>(`${serverUrl}/portal_skills/agent-targets`, jwt, {
80
102
  method: 'PATCH',
81
103
  body: {