@vimhead.dev/pi-norn 0.1.0-tip.36148588553.1 → 0.1.0-tip.36679386408.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.
- package/README.md +7 -3
- package/dist/index.js +142 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,9 +10,13 @@ pi install npm:@vimhead.dev/pi-norn@tip
|
|
|
10
10
|
pi
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Use [project-local runtime selection](https://github.com/vimhead/norn#project-local-runtime-selection)
|
|
14
|
+
with `.nornrc.json` to launch an installed CLI dependency without global Norn.
|
|
15
|
+
`pi --norn-executable /absolute/path/to/norn` overrides project selection;
|
|
16
|
+
without either, the adapter selects `norn` from `PATH`.
|
|
14
17
|
It does not carry another Norn runtime or SDK and does not inject authoring context
|
|
15
|
-
into Norn-managed agent sessions.
|
|
16
|
-
projects. Both introductions refresh at session
|
|
18
|
+
into Norn-managed agent sessions. Repository runtime configuration and workflow
|
|
19
|
+
discovery are skipped in untrusted projects. Both introductions refresh at session
|
|
20
|
+
start or `/reload`.
|
|
17
21
|
|
|
18
22
|
[Runtime installation](https://github.com/vimhead/norn#installation).
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,121 @@
|
|
|
1
1
|
// ../core/src/agent-protocol.ts
|
|
2
2
|
var AGENT_RESPONSE_TOOL_NAME = "pi_workflows_agent_response";
|
|
3
3
|
|
|
4
|
+
// ../core/src/runtime-resolution.mjs
|
|
5
|
+
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
6
|
+
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
7
|
+
var CLI_PACKAGE_NAME = "@vimhead.dev/norn-cli";
|
|
8
|
+
var RuntimeResolutionError = class extends Error {
|
|
9
|
+
};
|
|
10
|
+
async function resolveNornRuntime({
|
|
11
|
+
cwd,
|
|
12
|
+
executableOverride,
|
|
13
|
+
isProjectTrusted,
|
|
14
|
+
nodeExecutable
|
|
15
|
+
}) {
|
|
16
|
+
if (executableOverride !== null) {
|
|
17
|
+
if (typeof executableOverride !== "string" || !executableOverride.trim())
|
|
18
|
+
throw new RuntimeResolutionError(
|
|
19
|
+
"Norn executable override must be a nonempty executable path or name."
|
|
20
|
+
);
|
|
21
|
+
return { executable: executableOverride, args: [] };
|
|
22
|
+
}
|
|
23
|
+
if (!isProjectTrusted) return { executable: "norn", args: [] };
|
|
24
|
+
const configPath = await findAncestorEntry({
|
|
25
|
+
cwd,
|
|
26
|
+
relativePath: ".nornrc.json"
|
|
27
|
+
});
|
|
28
|
+
if (configPath === null) return { executable: "norn", args: [] };
|
|
29
|
+
const packageRoot = await readRuntimePackageRoot(configPath);
|
|
30
|
+
const scriptPath = await resolvePackageEntrypoint(packageRoot);
|
|
31
|
+
return { executable: nodeExecutable, args: [scriptPath] };
|
|
32
|
+
}
|
|
33
|
+
async function readRuntimePackageRoot(configPath) {
|
|
34
|
+
const configuration = await readManifest(configPath);
|
|
35
|
+
if (!isRecord(configuration) || Object.keys(configuration).some((key) => key !== "runtime") || !isRecord(configuration.runtime) || Object.keys(configuration.runtime).some((key) => key !== "packageRoot") || typeof configuration.runtime.packageRoot !== "string" || !configuration.runtime.packageRoot.trim())
|
|
36
|
+
throw new RuntimeResolutionError(
|
|
37
|
+
`Invalid ${JSON.stringify(configPath)}: expected { "runtime": { "packageRoot": "./path/to/package" } }.`
|
|
38
|
+
);
|
|
39
|
+
return resolve(dirname(configPath), configuration.runtime.packageRoot);
|
|
40
|
+
}
|
|
41
|
+
async function resolvePackageEntrypoint(packageRoot) {
|
|
42
|
+
const owner = await readManifest(join(packageRoot, "package.json"));
|
|
43
|
+
if (!isRecord(owner) || ![
|
|
44
|
+
owner.dependencies,
|
|
45
|
+
owner.devDependencies,
|
|
46
|
+
owner.optionalDependencies
|
|
47
|
+
].some(
|
|
48
|
+
(dependencies) => isRecord(dependencies) && typeof dependencies[CLI_PACKAGE_NAME] === "string"
|
|
49
|
+
))
|
|
50
|
+
throw new RuntimeResolutionError(
|
|
51
|
+
`The package at ${JSON.stringify(packageRoot)} must declare ${CLI_PACKAGE_NAME} as a dependency or devDependency.`
|
|
52
|
+
);
|
|
53
|
+
const packagePath = await findAncestorEntry({
|
|
54
|
+
cwd: packageRoot,
|
|
55
|
+
relativePath: `node_modules/${CLI_PACKAGE_NAME}`
|
|
56
|
+
});
|
|
57
|
+
if (packagePath === null)
|
|
58
|
+
throw new RuntimeResolutionError(
|
|
59
|
+
`Cannot find ${CLI_PACKAGE_NAME} from ${JSON.stringify(packageRoot)}. Install that package's dependencies and retry.`
|
|
60
|
+
);
|
|
61
|
+
const manifestPath = join(packagePath, "package.json");
|
|
62
|
+
const installedManifestPath = await realpath(manifestPath).catch(() => {
|
|
63
|
+
throw new RuntimeResolutionError(
|
|
64
|
+
`Cannot resolve the installed CLI at ${JSON.stringify(manifestPath)}. Reinstall the selected package's dependencies.`
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
const installed = await readManifest(installedManifestPath);
|
|
68
|
+
const entrypoint = isRecord(installed?.bin) ? installed.bin.norn : void 0;
|
|
69
|
+
if (installed?.name !== CLI_PACKAGE_NAME || typeof entrypoint !== "string" || !entrypoint.trim() || isAbsolute(entrypoint))
|
|
70
|
+
throw new RuntimeResolutionError(
|
|
71
|
+
`Invalid Norn CLI manifest at ${JSON.stringify(installedManifestPath)}: expected a relative bin.norn entrypoint.`
|
|
72
|
+
);
|
|
73
|
+
const installationRoot = dirname(installedManifestPath);
|
|
74
|
+
const scriptPath = resolve(installationRoot, entrypoint);
|
|
75
|
+
if (!scriptPath.startsWith(`${installationRoot}${sep}`))
|
|
76
|
+
throw new RuntimeResolutionError(
|
|
77
|
+
`Norn CLI entrypoint must be inside ${JSON.stringify(installationRoot)}.`
|
|
78
|
+
);
|
|
79
|
+
try {
|
|
80
|
+
if (!(await stat(scriptPath)).isFile()) throw new Error();
|
|
81
|
+
} catch {
|
|
82
|
+
throw new RuntimeResolutionError(
|
|
83
|
+
`Missing Norn CLI entrypoint at ${JSON.stringify(scriptPath)}. Reinstall the selected package's dependencies.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return scriptPath;
|
|
87
|
+
}
|
|
88
|
+
function isRecord(value) {
|
|
89
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
90
|
+
}
|
|
91
|
+
async function readManifest(path) {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
94
|
+
} catch {
|
|
95
|
+
throw new RuntimeResolutionError(
|
|
96
|
+
`Cannot read valid JSON from ${JSON.stringify(path)}. Check the selected package and configuration.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function findAncestorEntry({ cwd, relativePath }) {
|
|
101
|
+
let directory = resolve(cwd);
|
|
102
|
+
while (true) {
|
|
103
|
+
const candidate = join(directory, relativePath);
|
|
104
|
+
try {
|
|
105
|
+
await lstat(candidate);
|
|
106
|
+
return candidate;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.code !== "ENOENT")
|
|
109
|
+
throw new RuntimeResolutionError(
|
|
110
|
+
`Cannot inspect ${JSON.stringify(candidate)}.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const parent = dirname(directory);
|
|
114
|
+
if (parent === directory) return null;
|
|
115
|
+
directory = parent;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
4
119
|
// src/index.ts
|
|
5
120
|
var INTRO_START = "<norn-docs-intro>";
|
|
6
121
|
var INTRO_END = "</norn-docs-intro>";
|
|
@@ -8,7 +123,7 @@ function nornPiAdapter(pi) {
|
|
|
8
123
|
let cachedIntro;
|
|
9
124
|
pi.registerFlag("norn-executable", {
|
|
10
125
|
type: "string",
|
|
11
|
-
description: "Norn executable for docs intro and workflows intro (
|
|
126
|
+
description: "Norn executable override for docs intro and workflows intro (otherwise uses trusted .nornrc.json, then norn on PATH). Accepts an executable path, not a shell command."
|
|
12
127
|
});
|
|
13
128
|
pi.on("session_start", async (_event, ctx) => {
|
|
14
129
|
cachedIntro = void 0;
|
|
@@ -18,20 +133,32 @@ function nornPiAdapter(pi) {
|
|
|
18
133
|
const configuredExecutable = pi.getFlag("norn-executable");
|
|
19
134
|
if (configuredExecutable !== void 0 && typeof configuredExecutable !== "string")
|
|
20
135
|
throw new Error("Invalid Norn executable flag");
|
|
21
|
-
const
|
|
136
|
+
const isProjectTrusted = ctx.isProjectTrusted();
|
|
137
|
+
const invocation = await resolveNornRuntime({
|
|
138
|
+
cwd: ctx.cwd,
|
|
139
|
+
executableOverride: configuredExecutable ?? null,
|
|
140
|
+
isProjectTrusted,
|
|
141
|
+
nodeExecutable: process.versions.bun ? "node" : process.execPath
|
|
142
|
+
});
|
|
22
143
|
const [intro, workflowsIntro] = await Promise.all([
|
|
23
|
-
loadIntroduction({ pi,
|
|
24
|
-
|
|
144
|
+
loadIntroduction({ pi, invocation, cwd: ctx.cwd, group: "docs" }),
|
|
145
|
+
isProjectTrusted ? loadIntroduction({
|
|
25
146
|
pi,
|
|
26
|
-
|
|
147
|
+
invocation,
|
|
27
148
|
cwd: ctx.cwd,
|
|
28
149
|
group: "workflows"
|
|
29
150
|
}) : Promise.resolve("")
|
|
30
151
|
]);
|
|
31
|
-
cachedIntro = {
|
|
32
|
-
|
|
152
|
+
cachedIntro = {
|
|
153
|
+
configuredExecutable,
|
|
154
|
+
cwd: ctx.cwd,
|
|
155
|
+
isProjectTrusted,
|
|
156
|
+
intro,
|
|
157
|
+
workflowsIntro
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
33
160
|
ctx.ui.notify(
|
|
34
|
-
|
|
161
|
+
`Norn introduction unavailable.${error instanceof RuntimeResolutionError ? ` ${error.message}` : ""} Check --norn-executable or .nornrc.json and run the selected runtime with 'docs intro' and 'workflows intro' in the project directory to diagnose, then /reload to retry.`,
|
|
35
162
|
"warning"
|
|
36
163
|
);
|
|
37
164
|
}
|
|
@@ -41,10 +168,10 @@ function nornPiAdapter(pi) {
|
|
|
41
168
|
if (pi.getAllTools().some((tool) => tool.name === AGENT_RESPONSE_TOOL_NAME))
|
|
42
169
|
return;
|
|
43
170
|
if (event.systemPrompt.includes(INTRO_START)) return;
|
|
44
|
-
if (cachedIntro.
|
|
171
|
+
if (cachedIntro.configuredExecutable !== pi.getFlag("norn-executable") || cachedIntro.cwd !== ctx.cwd || cachedIntro.isProjectTrusted !== ctx.isProjectTrusted()) {
|
|
45
172
|
cachedIntro = void 0;
|
|
46
173
|
ctx.ui.notify(
|
|
47
|
-
"Norn
|
|
174
|
+
"Norn runtime selection or project trust changed. Run /reload to refresh its introduction.",
|
|
48
175
|
"warning"
|
|
49
176
|
);
|
|
50
177
|
return;
|
|
@@ -61,10 +188,11 @@ ${cachedIntro.workflowsIntro}` : ""}`
|
|
|
61
188
|
});
|
|
62
189
|
}
|
|
63
190
|
async function loadIntroduction(input) {
|
|
64
|
-
const result = await input.pi.exec(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
191
|
+
const result = await input.pi.exec(
|
|
192
|
+
input.invocation.executable,
|
|
193
|
+
[...input.invocation.args, input.group, "intro"],
|
|
194
|
+
{ cwd: input.cwd, timeout: 1e4 }
|
|
195
|
+
);
|
|
68
196
|
if (result.killed || result.code !== 0)
|
|
69
197
|
throw new Error(`Norn ${input.group} intro did not complete successfully`);
|
|
70
198
|
const response = JSON.parse(result.stdout);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vimhead.dev/pi-norn",
|
|
3
|
-
"version": "0.1.0-tip.
|
|
3
|
+
"version": "0.1.0-tip.36679386408.1",
|
|
4
4
|
"description": "Deliver installed Norn runtime documentation to outer Pi agent sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
41
|
+
"@earendil-works/pi-coding-agent": "0.99.1",
|
|
42
42
|
"@vimhead.dev/norn-core": "0.0.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|