@n8r/surface 0.0.0-stage → 0.1.0-experimental.5
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.
- package/LICENSE +202 -0
- package/LICENSE-DOCS +395 -0
- package/LICENSING.md +36 -0
- package/README.md +207 -2
- package/dist/activity.d.ts +16 -0
- package/dist/activity.js +44 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +412 -0
- package/dist/codec.d.ts +6 -0
- package/dist/codec.js +56 -0
- package/dist/codex.d.ts +72 -0
- package/dist/codex.js +526 -0
- package/dist/contracts.d.ts +235 -0
- package/dist/contracts.js +1 -0
- package/dist/cyclonedx-schemas/bom-1.7.schema.json +6700 -0
- package/dist/cyclonedx-schemas/cryptography-defs.schema.json +576 -0
- package/dist/cyclonedx-schemas/jsf-0.82.schema.json +240 -0
- package/dist/cyclonedx-schemas/spdx.schema.json +786 -0
- package/dist/cyclonedx-validation.d.ts +1 -0
- package/dist/cyclonedx-validation.js +45 -0
- package/dist/html.d.ts +5 -0
- package/dist/html.js +74 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/notes.d.ts +52 -0
- package/dist/notes.js +184 -0
- package/dist/portable.d.ts +10 -0
- package/dist/portable.js +71 -0
- package/dist/recent.d.ts +9 -0
- package/dist/recent.js +71 -0
- package/dist/revisions.d.ts +2 -0
- package/dist/revisions.js +60 -0
- package/dist/settings.d.ts +23 -0
- package/dist/settings.js +100 -0
- package/dist/setup.d.ts +9 -0
- package/dist/setup.js +161 -0
- package/dist/skill-artifacts-cli.d.ts +1 -0
- package/dist/skill-artifacts-cli.js +13 -0
- package/dist/skill-artifacts.d.ts +15 -0
- package/dist/skill-artifacts.js +184 -0
- package/dist/skill-cyclonedx.d.ts +26 -0
- package/dist/skill-cyclonedx.js +214 -0
- package/dist/skill-presentation.d.ts +10 -0
- package/dist/skill-presentation.js +108 -0
- package/dist/skill-surface-cli.d.ts +1 -0
- package/dist/skill-surface-cli.js +37 -0
- package/dist/skill-surface-store.d.ts +7 -0
- package/dist/skill-surface-store.js +126 -0
- package/dist/source-tools.d.ts +102 -0
- package/dist/source-tools.js +201 -0
- package/dist/startup.d.ts +7 -0
- package/dist/startup.js +74 -0
- package/dist/surface-bom.d.ts +73 -0
- package/dist/surface-bom.js +139 -0
- package/dist/surface-draft.d.ts +22 -0
- package/dist/surface-draft.js +112 -0
- package/dist/surface-instructions.d.ts +3 -0
- package/dist/surface-instructions.js +32 -0
- package/dist/surface-overview.d.ts +15 -0
- package/dist/surface-overview.js +86 -0
- package/dist/surface-store.d.ts +34 -0
- package/dist/surface-store.js +213 -0
- package/dist/surface-view.d.ts +3 -0
- package/dist/surface-view.js +50 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.js +482 -0
- package/docs/n8r-surface/README.md +133 -0
- package/docs/n8r-surface/cli-bundles.md +72 -0
- package/docs/n8r-surface/codex-research-transcript.md +51 -0
- package/docs/n8r-surface/codex-research-verification.md +176 -0
- package/docs/n8r-surface/command-outcomes.md +711 -0
- package/docs/n8r-surface/contracts.md +220 -0
- package/docs/n8r-surface/critical-path.md +184 -0
- package/docs/n8r-surface/design.md +289 -0
- package/docs/n8r-surface/first-use-access.md +143 -0
- package/docs/n8r-surface/first-use-overview.md +262 -0
- package/docs/n8r-surface/getting-started.md +74 -0
- package/docs/n8r-surface/guided-journey-verification.md +137 -0
- package/docs/n8r-surface/host-contract.md +176 -0
- package/docs/n8r-surface/implementation-decisions.md +96 -0
- package/docs/n8r-surface/instance-first-research.md +209 -0
- package/docs/n8r-surface/npm-distribution.md +107 -0
- package/docs/n8r-surface/npm-release.md +315 -0
- package/docs/n8r-surface/product-direction.md +327 -0
- package/docs/n8r-surface/recreation-handoff.md +117 -0
- package/docs/n8r-surface/researcher-discovery.md +157 -0
- package/docs/n8r-surface/researcher-prepared-check.md +117 -0
- package/docs/n8r-surface/researcher-reconciliation-trial.md +149 -0
- package/docs/n8r-surface/researcher-repository-check.md +95 -0
- package/docs/n8r-surface/researcher-skeleton-check.md +84 -0
- package/docs/n8r-surface/researcher-skill-verification.md +725 -0
- package/docs/n8r-surface/researcher-skill.md +309 -0
- package/docs/n8r-surface/researcher-vault.md +134 -0
- package/docs/n8r-surface/save-reopen-transcript.md +211 -0
- package/docs/n8r-surface/save-reopen-verification.md +152 -0
- package/docs/n8r-surface/security-assurance-strategy.md +267 -0
- package/docs/n8r-surface/skill-bom-verification.md +142 -0
- package/docs/n8r-surface/skill-cyclonedx-verification.md +144 -0
- package/docs/n8r-surface/skill-cyclonedx.md +85 -0
- package/docs/n8r-surface/skill-manual.md +129 -0
- package/docs/n8r-surface/skill-surface-artifacts.md +234 -0
- package/docs/n8r-surface/skill-usefulness-verification.md +135 -0
- package/docs/n8r-surface/skill-workspace-automation.md +359 -0
- package/docs/n8r-surface/startup-human-check.md +172 -0
- package/docs/n8r-surface/startup-verification.md +161 -0
- package/docs/n8r-surface/startup.md +151 -0
- package/docs/n8r-surface/surface-description-contract.md +224 -0
- package/docs/n8r-surface/surface-descriptions.md +173 -0
- package/docs/n8r-surface/surface-relationships.md +231 -0
- package/docs/n8r-surface/surface-revisions-transcript.md +552 -0
- package/docs/n8r-surface/surface-revisions-verification.md +161 -0
- package/docs/n8r-surface/tester-candidate.md +158 -0
- package/docs/n8r-surface/vault-setup-verification.md +210 -0
- package/docs/n8r-surface/verification.md +117 -0
- package/docs/n8r-surface/versioning.md +172 -0
- package/docs/n8r-surface/workspace-human-check.md +101 -0
- package/docs/n8r-surface/workspace-installation.md +68 -0
- package/docs/n8r-surface/workspace-verification.md +350 -0
- package/docs/n8r-surface/workspace.md +83 -0
- package/examples/researcher/README.md +100 -0
- package/examples/researcher/composed.surface.json +430 -0
- package/examples/researcher/composed.surface.yaml +283 -0
- package/examples/researcher/cyclonedx/configured/README.md +7 -0
- package/examples/researcher/cyclonedx/configured/manifest.json +10 -0
- package/examples/researcher/cyclonedx/configured/mapping.json +323 -0
- package/examples/researcher/cyclonedx/configured/surface-records.json +1275 -0
- package/examples/researcher/cyclonedx/configured/surface.cdx.json +79 -0
- package/examples/researcher/descriptions/README.md +29 -0
- package/examples/researcher/descriptions/bound-r1.json +141 -0
- package/examples/researcher/descriptions/bound-r2.json +153 -0
- package/examples/researcher/descriptions/capability.json +167 -0
- package/examples/researcher/descriptions/research-r1.json +153 -0
- package/examples/researcher/descriptions/research-r2.json +157 -0
- package/examples/researcher/descriptions/sources.md +24 -0
- package/examples/researcher/harbor-r1.research.html +193 -0
- package/examples/researcher/harbor-r2.research.html +226 -0
- package/examples/researcher/lumen-manual-edit.pending.html +271 -0
- package/examples/researcher/lumen-r1.research.html +271 -0
- package/examples/researcher/lumen-r2.research.html +280 -0
- package/examples/researcher/relationships/README.md +34 -0
- package/examples/researcher/relationships/bound-r1.json +346 -0
- package/examples/researcher/relationships/bound-r2.json +358 -0
- package/examples/researcher/relationships/capability.json +325 -0
- package/examples/researcher/relationships/config/assistant.env.example +4 -0
- package/examples/researcher/relationships/research-r1.json +300 -0
- package/examples/researcher/relationships/research-r2.json +304 -0
- package/examples/researcher/relationships/sources.md +38 -0
- package/examples/researcher/sources/harbor-api.md +16 -0
- package/examples/researcher/sources/harbor-auth.md +8 -0
- package/examples/researcher/sources/lumen-guide.md +28 -0
- package/examples/researcher/sources/lumen-settings.md +9 -0
- package/examples/researcher/surface-bom/README.md +17 -0
- package/examples/researcher/surface-bom/initial.cdx.json +188 -0
- package/package.json +45 -4
- package/plugins/n8r-surface/.codex-plugin/plugin.json +31 -0
- package/plugins/n8r-surface/LICENSE +202 -0
- package/plugins/n8r-surface/LICENSE-DOCS +395 -0
- package/plugins/n8r-surface/LICENSING.md +39 -0
- package/plugins/n8r-surface/assets/icon.svg +5 -0
- package/plugins/n8r-surface/plugin.json +27 -0
- package/plugins/n8r-surface/skills/research/SKILL.md +163 -0
- package/plugins/n8r-surface/skills/research/agents/openai.yaml +4 -0
- package/plugins/n8r-surface/skills/research/references/artifacts.md +152 -0
- package/plugins/n8r-surface/skills/research/references/cyclonedx.md +207 -0
- package/plugins/n8r-surface/skills/research/references/discovery-map.md +178 -0
- package/plugins/n8r-surface/skills/research/references/examples/bound-r1.json +346 -0
- package/plugins/n8r-surface/skills/research/references/examples/capability.json +325 -0
- package/plugins/n8r-surface/skills/research/references/examples/research-r1.json +300 -0
- package/plugins/n8r-surface/skills/research/references/examples/surface.cdx.json +188 -0
- package/plugins/n8r-surface/skills/research/references/first-use-access.md +97 -0
- package/plugins/n8r-surface/skills/research/references/first-use-overview.md +101 -0
- package/plugins/n8r-surface/skills/research/references/instance-first.md +125 -0
- package/plugins/n8r-surface/skills/research/references/model-meanings.md +55 -0
- package/plugins/n8r-surface/skills/research/references/skeleton-surface.md +105 -0
- package/plugins/n8r-surface/skills/research/references/working-notes.md +121 -0
- package/plugins/n8r-surface/skills/research/references/workspace.md +89 -0
- package/plugins/n8r-surface/skills/research/scripts/THIRD-PARTY-NOTICES.txt +273 -0
- package/plugins/n8r-surface/skills/research/scripts/artifacts.mjs +8348 -0
- package/plugins/n8r-surface/skills/research/scripts/cyclonedx.mjs +28113 -0
- package/plugins/n8r-surface/skills/research/scripts/surface.mjs +27800 -0
- package/plugins/n8r-surface/skills/research/scripts/workspace.mjs +120 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
import { recentWork, rememberWork } from './recent.js';
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
import { overview } from './startup.js';
|
|
7
|
+
import { chooseVault, inspectVault, saveSetup } from './setup.js';
|
|
8
|
+
import { line, problem, loadSettings, localPath, InspectionError } from './settings.js';
|
|
9
|
+
import { SourceTools } from './source-tools.js';
|
|
10
|
+
import { ResearchConversation } from './codex.js';
|
|
11
|
+
import { Activity } from './activity.js';
|
|
12
|
+
import { Notes, readNote, latestNote } from './notes.js';
|
|
13
|
+
import { SurfaceStore, readSurfacePair } from './surface-store.js';
|
|
14
|
+
import { previewSurface } from './surface-draft.js';
|
|
15
|
+
async function setup(config) {
|
|
16
|
+
const existing = config.vault === undefined ? undefined : inspectVault(config.vault);
|
|
17
|
+
if (existing && !existing.missing) {
|
|
18
|
+
console.log(`Vault ready: ${line(existing.root)}`);
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
22
|
+
console.log('Vault setup required; run n8r-surface in an interactive terminal.');
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
26
|
+
let closed = false;
|
|
27
|
+
input.once('close', () => { closed = true; });
|
|
28
|
+
const cancel = () => input.close();
|
|
29
|
+
input.on('SIGINT', cancel);
|
|
30
|
+
process.on('SIGINT', cancel);
|
|
31
|
+
const ask = (prompt) => new Promise(resolve => {
|
|
32
|
+
if (closed) {
|
|
33
|
+
resolve(undefined);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const ended = () => resolve(undefined);
|
|
37
|
+
input.once('close', ended);
|
|
38
|
+
input.question(prompt, answer => {
|
|
39
|
+
input.removeListener('close', ended);
|
|
40
|
+
resolve(answer);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
let vault = existing?.root;
|
|
45
|
+
if (vault === undefined) {
|
|
46
|
+
const answer = await ask('Vault directory [~/n8r/]: ');
|
|
47
|
+
if (answer === undefined)
|
|
48
|
+
return 1;
|
|
49
|
+
vault = chooseVault(answer === '' ? '~/n8r/' : answer, config.home, process.cwd());
|
|
50
|
+
inspectVault(vault);
|
|
51
|
+
}
|
|
52
|
+
console.log(`Vault directory: ${line(vault)}`);
|
|
53
|
+
if (existing)
|
|
54
|
+
console.log('Required vault folders are missing; confirmation will create them.');
|
|
55
|
+
const answer = await ask('Create vault folders and save this location? [y/N] ');
|
|
56
|
+
if (answer === undefined || !/^y(es)?$/i.test(answer.trim()))
|
|
57
|
+
return 1;
|
|
58
|
+
const saved = saveSetup(config, vault, process.cwd());
|
|
59
|
+
console.log(`Vault ready: ${line(saved)}`);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
input.close();
|
|
64
|
+
process.removeListener('SIGINT', cancel);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const help = `n8r-surface — experimental local AI Surface research
|
|
68
|
+
|
|
69
|
+
n8r-surface Open a conversation or return to saved work
|
|
70
|
+
n8r-surface discover [source] Research a directory or file (default: cwd)
|
|
71
|
+
n8r-surface discover [source] --model MODEL
|
|
72
|
+
n8r-surface reopen [NOTE] --source SOURCE [--model MODEL]
|
|
73
|
+
Continue a note or pair; omit NOTE for latest edited note
|
|
74
|
+
n8r-surface --help Show this help
|
|
75
|
+
n8r-surface --version Show package version
|
|
76
|
+
|
|
77
|
+
Discovery requires a configured vault, an interactive terminal, and Codex CLI
|
|
78
|
+
0.166.0 signed in with ChatGPT. You confirm before source text is sent to OpenAI.
|
|
79
|
+
Uses your configured Codex model unless --model selects one; no fallback.
|
|
80
|
+
Source tools list, search and read bounded UTF-8 text. Hidden entries, dependency
|
|
81
|
+
folders, symlinks, key files and oversized/binary files are excluded.
|
|
82
|
+
No source execution, edits or web access. Type save to save a checked Surface.
|
|
83
|
+
At startup, /source PATH changes the working source; choose saved work by number.
|
|
84
|
+
/save note writes Markdown; legacy discover/reopen /save still writes notes.
|
|
85
|
+
Ask for a Surface to preview it; /save surface saves that checked bundle.
|
|
86
|
+
Reopen either research.html or surface.json from a saved pair to revise it.
|
|
87
|
+
Reopen confirms note/source transmission again; no old Codex thread is restored.
|
|
88
|
+
Codex manages its own auth/cache/diagnostics. /exit ends the chat; Ctrl-C cancels.`;
|
|
89
|
+
async function discover(args, reopening = false, guided = false, firstQuestion) {
|
|
90
|
+
let source, model, notePath;
|
|
91
|
+
for (let i = 0; i < args.length; i++) {
|
|
92
|
+
const arg = args[i];
|
|
93
|
+
if (arg === '--model' && model === undefined && args[i + 1] && /^[a-zA-Z0-9._-]{1,100}$/.test(args[i + 1]))
|
|
94
|
+
model = args[++i];
|
|
95
|
+
else if (reopening && arg === '--source' && source === undefined && args[i + 1] && !args[i + 1].startsWith('-'))
|
|
96
|
+
source = args[++i];
|
|
97
|
+
else if (reopening && !arg.startsWith('-') && notePath === undefined)
|
|
98
|
+
notePath = arg;
|
|
99
|
+
else if (!reopening && !arg.startsWith('-') && source === undefined)
|
|
100
|
+
source = arg;
|
|
101
|
+
else
|
|
102
|
+
throw new InspectionError('Unsupported arguments; run n8r-surface discover --help');
|
|
103
|
+
}
|
|
104
|
+
if (reopening && !source)
|
|
105
|
+
throw new InspectionError('Reopen requires --source SOURCE; source access must be selected again');
|
|
106
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
107
|
+
console.log('Discovery requires an interactive terminal; no research started.');
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
const config = loadSettings();
|
|
111
|
+
if (!config.vault || inspectVault(config.vault).missing) {
|
|
112
|
+
console.log('Vault setup required; run n8r-surface first.');
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
const tools = new SourceTools(localPath(source ?? process.cwd(), config.home, process.cwd()));
|
|
116
|
+
console.log(`Research source: ${line(tools.selected)} (${tools.file ? 'one file' : 'directory'})`);
|
|
117
|
+
console.log('Inspected source text and your messages will be sent to OpenAI through Codex using ChatGPT sign-in.');
|
|
118
|
+
console.log(guided ? 'Your source stays unchanged. Type save when you want to keep the result.' : 'Source access is read-only. /save saves a Markdown note; /save surface saves the previewed Surface bundle.');
|
|
119
|
+
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
let closed = false, interrupted = false;
|
|
122
|
+
let session;
|
|
123
|
+
input.once('close', () => { closed = true; controller.abort(); });
|
|
124
|
+
const cancel = () => { interrupted = true; controller.abort(); input.close(); };
|
|
125
|
+
input.on('SIGINT', cancel);
|
|
126
|
+
process.on('SIGINT', cancel);
|
|
127
|
+
const ask = (prompt) => new Promise(resolve => {
|
|
128
|
+
if (closed) {
|
|
129
|
+
resolve(undefined);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const ended = () => resolve(undefined);
|
|
133
|
+
input.once('close', ended);
|
|
134
|
+
input.question(prompt, answer => { input.removeListener('close', ended); resolve(answer); });
|
|
135
|
+
});
|
|
136
|
+
const activity = new Activity();
|
|
137
|
+
const output = (text) => activity.log((guided ? text.replace(/ sha256:[a-f0-9]{64}/g, '') : text).split('\n').map(part => line(part)).join('\n') + '\n');
|
|
138
|
+
try {
|
|
139
|
+
let note;
|
|
140
|
+
let pair;
|
|
141
|
+
if (reopening && notePath && /(?:research\.html|surface\.json)$/.test(notePath)) {
|
|
142
|
+
pair = readSurfacePair(localPath(notePath, config.home, process.cwd()));
|
|
143
|
+
console.log(`Saved pair: ${line(pair.directory)}`);
|
|
144
|
+
console.log('Saved artefacts, dependencies and host evidence will be sent to OpenAI after confirmation.');
|
|
145
|
+
}
|
|
146
|
+
else if (reopening) {
|
|
147
|
+
note = notePath ? readNote(localPath(notePath, config.home, process.cwd())) : latestNote(config.vault, tools.selected);
|
|
148
|
+
if (!note) {
|
|
149
|
+
console.log('No saved numbered note found for this source. Name a note explicitly or start with discover.');
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
console.log(`Saved note: ${line(note.path)}`);
|
|
153
|
+
console.log(`Last edited: ${note.modifiedAt}`);
|
|
154
|
+
console.log(`Recorded source: ${line(note.metadata.source.name)} (${note.metadata.source.kind}); this is not verified identity.`);
|
|
155
|
+
if (!notePath) {
|
|
156
|
+
const choice = await ask('Open this note? [y/N] ');
|
|
157
|
+
if (interrupted)
|
|
158
|
+
return 130;
|
|
159
|
+
if (choice === undefined || !/^y(es)?$/i.test(choice.trim()))
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
console.log('The saved note, including human edits, will also be sent to OpenAI after confirmation.');
|
|
163
|
+
}
|
|
164
|
+
const notes = new Notes(config.vault, tools.selected, note);
|
|
165
|
+
const surfaces = new SurfaceStore(config.vault, tools.selected, pair, note);
|
|
166
|
+
const accepted = await ask('Start research with OpenAI using Codex ChatGPT sign-in? [y/N] ');
|
|
167
|
+
if (interrupted)
|
|
168
|
+
return 130;
|
|
169
|
+
if (accepted === undefined || !/^y(es)?$/i.test(accepted.trim()))
|
|
170
|
+
return 1;
|
|
171
|
+
console.log('Connecting to Codex…');
|
|
172
|
+
session = await activity.run('Connecting to Codex', () => ResearchConversation.start(tools, output, model, controller.signal, guided));
|
|
173
|
+
console.log(`Connected: ChatGPT sign-in; model ${line(session.model)}.`);
|
|
174
|
+
if (!guided)
|
|
175
|
+
console.log(`Researcher ${session.instructions.version}; ${session.instructions.digest}`);
|
|
176
|
+
console.log(guided ? 'Ask questions, explore another angle, or make corrections. Type save to keep the result; exit to leave.' : 'Ask about your source or request a Surface preview. /save saves a note; /save surface saves the preview; /exit ends the chat; Ctrl-C cancels.');
|
|
177
|
+
if (note)
|
|
178
|
+
await activity.run('Reopening research', () => session.reopen(note));
|
|
179
|
+
if (pair)
|
|
180
|
+
await activity.run('Reopening Surface', () => session.reopenSurface(pair));
|
|
181
|
+
if (firstQuestion)
|
|
182
|
+
await activity.run('Researching', () => session.turn(firstQuestion));
|
|
183
|
+
if (session.surfaceDraft)
|
|
184
|
+
output(previewSurface(session.surfaceDraft));
|
|
185
|
+
let dirty = Boolean(firstQuestion), lastSaved;
|
|
186
|
+
const remember = (item) => { try {
|
|
187
|
+
rememberWork(config.home, item);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.log(`The result is saved, but the recent-work list could not be updated: ${problem(error)}. Keep the saved path above.`);
|
|
192
|
+
return false;
|
|
193
|
+
} };
|
|
194
|
+
while (!closed) {
|
|
195
|
+
const question = await ask('You> ');
|
|
196
|
+
if (question === undefined || /^(?:\/exit|exit|quit)$/i.test(question.trim()))
|
|
197
|
+
break;
|
|
198
|
+
if (!question.trim())
|
|
199
|
+
continue;
|
|
200
|
+
const plainSave = /^(?:please )?save(?: (?:it|this|the result|my research|the research|this result|the surface))?(?: for later)?[.!]?$/i.test(question.trim());
|
|
201
|
+
if (plainSave || question.trim() === '/save surface' || (guided && question.trim() === '/save')) {
|
|
202
|
+
if (!dirty && lastSaved) {
|
|
203
|
+
console.log(`Already saved: ${line(lastSaved)}`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
if (!session.surfaceDraft && (plainSave || guided)) {
|
|
208
|
+
await activity.run('Preparing your saved result', () => session.turn('HOST_SURFACE_SAVE_REQUEST: The user asked to save the result. Prepare the checked Surface bundle now using surface_draft. Continue the saved Surface kind and identities if reopened; otherwise create a Capability Surface from the research so far. Retain decisions, evidence and unknowns. Inspect permitted sources as needed. Stage the result; the host will save it.'));
|
|
209
|
+
}
|
|
210
|
+
if (!session.surfaceDraft) {
|
|
211
|
+
console.log('No checked result is ready to save yet. Continue the research or ask for a Surface, then try save again.');
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const saved = surfaces.save(session.surfaceDraft, { researcher: { version: session.instructions.version, digest: session.instructions.digest }, model: session.model, evidence: session.evidence });
|
|
215
|
+
session.acceptSurface(saved);
|
|
216
|
+
console.log(`Saved and read back: ${line(saved.briefPath ?? saved.researchPath)}`);
|
|
217
|
+
console.log(`Saved and read back: ${line(saved.surfacePath)}`);
|
|
218
|
+
const remembered = remember({ id: saved.surface.id, title: saved.surface.title, source: tools.selected, path: saved.surfacePath, model: session.model });
|
|
219
|
+
if (remembered)
|
|
220
|
+
console.log(`Next time, open n8r-surface and choose “${line(saved.surface.title)}”.`);
|
|
221
|
+
lastSaved = saved.briefPath ?? saved.researchPath;
|
|
222
|
+
dirty = false;
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.log(`Could not complete the save: ${problem(error)}. Your earlier saved work is unchanged. You can continue this conversation.`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (question.trim() === '/save' || question.trim() === '/save note') {
|
|
229
|
+
const destination = notes.next();
|
|
230
|
+
console.log(`Save destination: ${line(destination)}`);
|
|
231
|
+
const markdown = await activity.run('Preparing note', () => session.prepareNote());
|
|
232
|
+
if (closed || controller.signal.aborted)
|
|
233
|
+
throw new InspectionError('Research cancelled; nothing saved');
|
|
234
|
+
const saved = notes.save(destination, markdown, { researcher: { version: session.instructions.version, digest: session.instructions.digest }, model: session.model, evidence: session.evidence });
|
|
235
|
+
console.log(`Saved and read back: ${line(saved.path)}`);
|
|
236
|
+
if (!guided)
|
|
237
|
+
console.log(saved.digest);
|
|
238
|
+
remember({ id: `note:${tools.selected}`, title: markdown.split('\n').find(x => x.startsWith('# '))?.slice(2) ?? basename(tools.selected), source: tools.selected, path: saved.path, model: session.model });
|
|
239
|
+
}
|
|
240
|
+
else if (question.trim().startsWith('/')) {
|
|
241
|
+
console.log('Type save to keep the result, /save note for a Markdown note, or exit to leave.');
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
dirty = true;
|
|
245
|
+
await activity.run('Waiting for Codex', () => session.turn(question));
|
|
246
|
+
if (session.surfaceDraft)
|
|
247
|
+
output(previewSurface(session.surfaceDraft));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return interrupted ? 130 : 0;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (interrupted) {
|
|
254
|
+
console.log('Research cancelled.');
|
|
255
|
+
return 130;
|
|
256
|
+
}
|
|
257
|
+
if (closed) {
|
|
258
|
+
console.log('Research input closed; answer incomplete.');
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
if (session)
|
|
265
|
+
await session.close();
|
|
266
|
+
input.close();
|
|
267
|
+
process.removeListener('SIGINT', cancel);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Open conversation; saved-work choices are shortcuts, not a required sequence. */
|
|
271
|
+
async function launch() {
|
|
272
|
+
let config = loadSettings();
|
|
273
|
+
console.log('n8r-surface');
|
|
274
|
+
const ready = await setup(config);
|
|
275
|
+
if (ready !== 0)
|
|
276
|
+
return ready;
|
|
277
|
+
config = loadSettings();
|
|
278
|
+
let work = [];
|
|
279
|
+
try {
|
|
280
|
+
work = recentWork(config.home);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.log(`Saved-work list unavailable: ${problem(error)}. Your saved files are unchanged.`);
|
|
284
|
+
}
|
|
285
|
+
let source = process.cwd(), override, pendingQuestion;
|
|
286
|
+
const checkNewSource = (path) => { const tools = new SourceTools(path); new Notes(config.vault, tools.selected); new SurfaceStore(config.vault, tools.selected); return tools.selected; };
|
|
287
|
+
console.log(`Working folder: ${line(source)}`);
|
|
288
|
+
if (work.length) {
|
|
289
|
+
console.log('Continue saved work:');
|
|
290
|
+
work.slice(0, 20).forEach((w, i) => console.log(` ${i + 1}. ${line(w.title)} — ${line(basename(w.source))}`));
|
|
291
|
+
}
|
|
292
|
+
console.log('Tell me what you want to investigate, or choose saved work by number.');
|
|
293
|
+
console.log('You can ask questions and change direction. /source PATH changes the working folder or file. exit leaves.');
|
|
294
|
+
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
295
|
+
let closed = false;
|
|
296
|
+
input.once('close', () => { closed = true; });
|
|
297
|
+
const cancel = () => input.close();
|
|
298
|
+
input.on('SIGINT', cancel);
|
|
299
|
+
process.on('SIGINT', cancel);
|
|
300
|
+
const ask = (prompt) => new Promise(resolve => {
|
|
301
|
+
if (closed) {
|
|
302
|
+
resolve(undefined);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const end = () => resolve(undefined);
|
|
306
|
+
input.once('close', end);
|
|
307
|
+
input.question(prompt, answer => { input.removeListener('close', end); resolve(answer); });
|
|
308
|
+
});
|
|
309
|
+
let selection;
|
|
310
|
+
try {
|
|
311
|
+
while (!closed) {
|
|
312
|
+
const answer = await ask('You> ');
|
|
313
|
+
if (answer === undefined || /^(?:exit|quit|\/exit)$/i.test(answer.trim()))
|
|
314
|
+
return 0;
|
|
315
|
+
const value = answer.trim();
|
|
316
|
+
if (!value)
|
|
317
|
+
continue;
|
|
318
|
+
if (value.startsWith('/source ')) {
|
|
319
|
+
try {
|
|
320
|
+
source = checkNewSource(localPath(value.slice(8).trim(), config.home, process.cwd()));
|
|
321
|
+
override = source;
|
|
322
|
+
console.log(`Working source: ${line(source)}`);
|
|
323
|
+
if (pendingQuestion) {
|
|
324
|
+
selection = { args: [source], reopening: false, question: pendingQuestion };
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
console.log(`Source unavailable: ${problem(e)}`);
|
|
330
|
+
}
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (/^\d+$/.test(value)) {
|
|
334
|
+
const item = work[Number(value) - 1];
|
|
335
|
+
if (!item) {
|
|
336
|
+
console.log('Choose one of the displayed numbers, or describe new research.');
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const selectedSource = override ?? item.source;
|
|
340
|
+
if (!existsSync(selectedSource)) {
|
|
341
|
+
console.log('The remembered source has moved. Use /source PATH to select it, then choose the saved work again.');
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const note = item.path.endsWith('.md') ? readNote(item.path) : undefined;
|
|
346
|
+
const pair = note ? undefined : readSurfacePair(item.path);
|
|
347
|
+
new SourceTools(selectedSource);
|
|
348
|
+
new Notes(config.vault, selectedSource, note);
|
|
349
|
+
new SurfaceStore(config.vault, selectedSource, pair, note);
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
console.log(`Cannot reopen this saved work: ${problem(e)}. Its files have not been changed.`);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
selection = { args: [item.path, '--source', selectedSource, '--model', item.model], reopening: true };
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
if (value.startsWith('/')) {
|
|
359
|
+
console.log('Use /source PATH, select saved work by number, or simply ask a research question.');
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
source = checkNewSource(source);
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
pendingQuestion = answer;
|
|
367
|
+
console.log(`This folder cannot be used for research: ${problem(e)}. Select your project with /source PATH; I will keep your question.`);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
selection = { args: [source], reopening: false, question: answer };
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
input.close();
|
|
376
|
+
process.removeListener('SIGINT', cancel);
|
|
377
|
+
}
|
|
378
|
+
if (!selection)
|
|
379
|
+
return 0;
|
|
380
|
+
return discover(selection.args, selection.reopening, true, selection.question);
|
|
381
|
+
}
|
|
382
|
+
const args = process.argv.slice(2);
|
|
383
|
+
try {
|
|
384
|
+
if ((args.length === 1 && args[0] === '--help') || (args.length === 2 && ['discover', 'reopen'].includes(args[0]) && args[1] === '--help'))
|
|
385
|
+
console.log(help);
|
|
386
|
+
else if (args.length === 1 && args[0] === '--version')
|
|
387
|
+
console.log(`n8r-surface ${JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version}`);
|
|
388
|
+
else if (args[0] === 'reopen')
|
|
389
|
+
process.exitCode = await discover(args.slice(1), true);
|
|
390
|
+
else if (args[0] === 'discover')
|
|
391
|
+
process.exitCode = await discover(args.slice(1));
|
|
392
|
+
else if (args.length) {
|
|
393
|
+
console.log('Unsupported arguments; run n8r-surface --help.');
|
|
394
|
+
process.exitCode = 2;
|
|
395
|
+
}
|
|
396
|
+
else if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
397
|
+
process.exitCode = await launch();
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
const state = overview();
|
|
401
|
+
console.log(state.text);
|
|
402
|
+
console.log('Setup does not start research. Use n8r-surface discover to begin a conversation.');
|
|
403
|
+
if (state.failed || !state.settings)
|
|
404
|
+
process.exitCode = 2;
|
|
405
|
+
else
|
|
406
|
+
process.exitCode = await setup(state.settings);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
console.log(`n8r-surface failed: ${problem(error)}`);
|
|
411
|
+
process.exitCode = 2;
|
|
412
|
+
}
|
package/dist/codec.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Artefact } from './contracts.js';
|
|
2
|
+
export type Encoding = 'json' | 'yaml';
|
|
3
|
+
/** Strict JSON syntax for host envelopes; artifact validation is separate. */
|
|
4
|
+
export declare function decodeJsonValue(text: string): unknown;
|
|
5
|
+
export declare function decode(text: string, format: Encoding): Artefact;
|
|
6
|
+
export declare function encode(value: unknown, format: Encoding): string;
|
package/dist/codec.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isAlias, isMap, isNode, isScalar, parseDocument, stringify, visit } from 'yaml';
|
|
2
|
+
import { checkSize, fail, MAX_DEPTH } from './portable.js';
|
|
3
|
+
import { validateArtefact } from './validate.js';
|
|
4
|
+
function formatCheck(format) { if (format !== 'json' && format !== 'yaml')
|
|
5
|
+
fail('$format', 'unsupported encoding'); }
|
|
6
|
+
function decodeValue(text, format) {
|
|
7
|
+
formatCheck(format);
|
|
8
|
+
checkSize(text);
|
|
9
|
+
if (format === 'json') {
|
|
10
|
+
try {
|
|
11
|
+
JSON.parse(text);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
fail('$', 'invalid JSON syntax');
|
|
15
|
+
}
|
|
16
|
+
} // enforce JSON grammar before YAML's duplicate/number checks
|
|
17
|
+
if (/^%/m.test(text))
|
|
18
|
+
fail('$', 'YAML directives unsupported');
|
|
19
|
+
const doc = parseDocument(text, { version: '1.2', schema: 'core', uniqueKeys: true, strict: true });
|
|
20
|
+
if (doc.errors.length || doc.warnings.length)
|
|
21
|
+
fail('$', 'invalid encoded syntax or duplicate keys');
|
|
22
|
+
visit(doc, (_key, node, path) => {
|
|
23
|
+
if (path.length > MAX_DEPTH * 2 + 2)
|
|
24
|
+
fail('$', 'exceeds depth 64');
|
|
25
|
+
if (isAlias(node) || (isNode(node) && (node.tag || ('anchor' in node && node.anchor))))
|
|
26
|
+
fail('$', 'YAML tags/anchors/aliases unsupported');
|
|
27
|
+
if (isMap(node))
|
|
28
|
+
for (const pair of node.items) {
|
|
29
|
+
if (!isScalar(pair.key) || typeof pair.key.value !== 'string')
|
|
30
|
+
fail('$', 'mapping keys must be strings');
|
|
31
|
+
if (format === 'yaml' && pair.key.value === '<<' && pair.key.type !== 'QUOTE_DOUBLE' && pair.key.type !== 'QUOTE_SINGLE')
|
|
32
|
+
fail('$', 'unquoted YAML merge keys unsupported');
|
|
33
|
+
}
|
|
34
|
+
if (isScalar(node) && typeof node.value === 'number' && !/^-?(0|[1-9][0-9]*)$/.test(node.source ?? ''))
|
|
35
|
+
fail('$', 'numbers require decimal integer notation');
|
|
36
|
+
});
|
|
37
|
+
const data = format === 'json' ? JSON.parse(text) : doc.toJS({ maxAliasCount: 0 });
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
/** Strict JSON syntax for host envelopes; artifact validation is separate. */
|
|
41
|
+
export function decodeJsonValue(text) { return decodeValue(text, 'json'); }
|
|
42
|
+
export function decode(text, format) {
|
|
43
|
+
const a = validateArtefact(decodeValue(text, format));
|
|
44
|
+
if ((a.contract === 'n8r.surface/0.2' || a.contract === 'n8r.surface/0.3') && format !== 'json')
|
|
45
|
+
fail('$format', 'Surface descriptions require JSON');
|
|
46
|
+
return a;
|
|
47
|
+
}
|
|
48
|
+
export function encode(value, format) {
|
|
49
|
+
formatCheck(format);
|
|
50
|
+
const a = validateArtefact(value);
|
|
51
|
+
if ((a.contract === 'n8r.surface/0.2' || a.contract === 'n8r.surface/0.3') && format !== 'json')
|
|
52
|
+
fail('$format', 'Surface descriptions require JSON');
|
|
53
|
+
const text = format === 'json' ? JSON.stringify(a, null, 2) + '\n' : stringify(a, { version: '1.2', aliasDuplicateObjects: false, lineWidth: 0, defaultKeyType: 'QUOTE_DOUBLE' });
|
|
54
|
+
checkSize(text);
|
|
55
|
+
return text;
|
|
56
|
+
}
|
package/dist/codex.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import { SourceTools } from './source-tools.js';
|
|
3
|
+
import { type Evidence, type Note } from './notes.js';
|
|
4
|
+
import { type SurfaceDraft } from './surface-draft.js';
|
|
5
|
+
import { type SavedSurfacePair } from './surface-store.js';
|
|
6
|
+
type Wire = Record<string, any>;
|
|
7
|
+
export declare class CodexRpc {
|
|
8
|
+
private child;
|
|
9
|
+
private event;
|
|
10
|
+
private serve;
|
|
11
|
+
private sequence;
|
|
12
|
+
private pending;
|
|
13
|
+
private failure;
|
|
14
|
+
private buffer;
|
|
15
|
+
constructor(child: ChildProcessWithoutNullStreams, event: (method: string, params: Wire) => void, serve: (method: string, params: Wire) => Promise<unknown>);
|
|
16
|
+
private send;
|
|
17
|
+
private receive;
|
|
18
|
+
private fail;
|
|
19
|
+
request(method: string, params: unknown, timeout?: number): Promise<Wire>;
|
|
20
|
+
notify(method: string): void;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare function researcherInstructions(): {
|
|
24
|
+
version: string;
|
|
25
|
+
digest: string;
|
|
26
|
+
text: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function runnerConfiguration(current: Wire): Wire;
|
|
29
|
+
export declare class ResearchConversation {
|
|
30
|
+
private source;
|
|
31
|
+
private output;
|
|
32
|
+
private signal?;
|
|
33
|
+
private rpc;
|
|
34
|
+
private scratch;
|
|
35
|
+
private threadId;
|
|
36
|
+
readonly evidence: Evidence[];
|
|
37
|
+
surfaceDraft: SurfaceDraft | undefined;
|
|
38
|
+
private pendingSurface;
|
|
39
|
+
private previousSurface;
|
|
40
|
+
private saving;
|
|
41
|
+
private draft;
|
|
42
|
+
private turnId;
|
|
43
|
+
private earlyEvents;
|
|
44
|
+
private ready;
|
|
45
|
+
private markReady;
|
|
46
|
+
private active;
|
|
47
|
+
private cancelled;
|
|
48
|
+
private closing;
|
|
49
|
+
private abortListener;
|
|
50
|
+
readonly instructions: {
|
|
51
|
+
version: string;
|
|
52
|
+
digest: string;
|
|
53
|
+
text: string;
|
|
54
|
+
};
|
|
55
|
+
model: string;
|
|
56
|
+
private constructor();
|
|
57
|
+
static start(source: SourceTools, output: (text: string) => void, model?: string, signal?: AbortSignal, conversational?: boolean): Promise<ResearchConversation>;
|
|
58
|
+
private event;
|
|
59
|
+
private serve;
|
|
60
|
+
private finish;
|
|
61
|
+
private recordEvidence;
|
|
62
|
+
prepareNote(): Promise<string>;
|
|
63
|
+
acceptSurface(draft: SurfaceDraft): void;
|
|
64
|
+
reopenSurface(pair: SavedSurfacePair): Promise<void>;
|
|
65
|
+
reopen(note: Note): Promise<void>;
|
|
66
|
+
private reopenContext;
|
|
67
|
+
turn(text: string): Promise<void>;
|
|
68
|
+
private runTurn;
|
|
69
|
+
cancel(): Promise<void>;
|
|
70
|
+
close(): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
export {};
|