@vimhead.dev/norn-cli 0.1.0-tip.35725822562.1 → 0.1.0-tip.36148588553.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/assets/README.md CHANGED
@@ -149,8 +149,9 @@ methods, custom providers, and Norn's configuration directory, see
149
149
 
150
150
  ### 3. Optionally connect your harness
151
151
 
152
- The shipped Pi and Cursor adapters deliver Norn documentation context to your
153
- harness. Installing Norn alone does not register an adapter. Claude Code, Codex,
152
+ The shipped Pi, Cursor, and Claude Code adapters deliver Norn documentation context and
153
+ [available workflow introductions](docs/cli.md#workflow-introduction) to your
154
+ harness at session start. Installing Norn alone does not register an adapter. Codex
154
155
  and other harnesses can [invoke the CLI directly](docs/cli.md#javascript-client-and-other-harnesses).
155
156
 
156
157
  #### Pi
@@ -183,6 +184,32 @@ outside `PATH`, start Cursor with an executable path:
183
184
  NORN_EXECUTABLE=/absolute/path/to/norn cursor .
184
185
  ```
185
186
 
187
+ #### Claude Code
188
+
189
+ With Node and `norn` available on `PATH`, run in your terminal:
190
+
191
+ ```bash
192
+ claude plugin marketplace add vimhead/norn
193
+ claude plugin install norn@norn-adapters
194
+ claude
195
+ ```
196
+
197
+ To select a different runtime, launch Claude Code with
198
+ `NORN_EXECUTABLE=/absolute/path/to/norn claude`. The plugin uses that runtime's
199
+ documentation and the workflows in the session's working directory; it does not
200
+ install Norn or configure authentication for Norn agents.
201
+
202
+ Context is added on startup, `/clear`, and compaction, not again on resume or
203
+ fork. Named-agent sessions (`--agent`) and subagent hook calls are excluded.
204
+ Existing instructions and prompts are preserved. Introduction failures appear as
205
+ hook errors without falling back to another runtime.
206
+
207
+ To test a checkout without installing the marketplace:
208
+
209
+ ```bash
210
+ claude --plugin-dir /absolute/path/to/norn
211
+ ```
212
+
186
213
  Need an adapter for another harness? [Open an issue](https://github.com/vimhead/norn/issues/new)
187
214
  with the harness name.
188
215
 
@@ -96,6 +96,31 @@ each argument, without relying on another `norn` installation on PATH.
96
96
  inputs without filesystem or process access. Generating the introduction does not
97
97
  inject it into prompts or alter Norn agent sessions.
98
98
 
99
+ ## Workflow introduction
100
+
101
+ ```bash
102
+ norn workflows intro
103
+ ```
104
+
105
+ Returns `{ "intro": "..." }` with caller guidance and an XML advertisement of the
106
+ current project's entrypoints. Each `<workflow>` contains its fully qualified
107
+ `<id>` and caller-facing `<instructions>` inside `<available_norn_workflows>`;
108
+ field values are XML-escaped. Internal steps and argument schemas are omitted.
109
+ For example, the [minimal project](../examples/minimal-workflow/README.md)
110
+ advertises `greet`; its full contract remains available through `workflows inspect greet`.
111
+
112
+ The command discovers the nearest project from the invocation directory and
113
+ loads current registration modules. No project, or a valid project with no
114
+ entrypoints, returns `{ "intro": "" }` with no preamble or XML wrapper. Invalid
115
+ configuration or incomplete registration fails rather than returning an empty
116
+ or partial advertisement. Like [other discovery commands](projects.md#import-and-reload),
117
+ it evaluates module-level code but does not execute workflows or create run state.
118
+
119
+ Adapters can request `docs intro` and `workflows intro` from the same selected
120
+ executable and deliver the returned text in that order. Neither command injects
121
+ context itself. The advertisement is a snapshot, not a replacement for live
122
+ workflow listing and inspection after source changes.
123
+
99
124
  ## Discover live contracts
100
125
 
101
126
  ```bash
@@ -1 +1 @@
1
- {"version":"0.1.0-tip.35725822562.1"}
1
+ {"version":"0.1.0-tip.36148588553.1"}
@@ -67,6 +67,8 @@ import {
67
67
  NORN_PROJECT_FILE_NAME,
68
68
  } from "./workflow-loader.ts";
69
69
 
70
+ import { loadNornWorkflowsIntro } from "./workflows-intro.ts";
71
+
70
72
  const RUNS_ROOT = join(".norn", "runs");
71
73
  const RUN_WAIT_INTERVAL_MS = 1000;
72
74
  const CLI_DESCRIPTION =
@@ -215,6 +217,20 @@ const COMMANDS: readonly CliCommand[] = [
215
217
  examples: ["norn workflows list", "norn workflows list --all"],
216
218
  execute: listWorkflows,
217
219
  },
220
+ {
221
+ id: "workflows.intro",
222
+ path: ["workflows", "intro"],
223
+ description:
224
+ "Produce caller guidance and an XML advertisement of the current project's entrypoint workflows. Loads project modules but does not execute workflows or deliver context to agents.",
225
+ usage: "norn workflows intro",
226
+ output:
227
+ "JSON object with text under intro. Empty when no project or entrypoints exist; project-loading failures are errors, not partial advertisements.",
228
+ examples: ["norn workflows intro"],
229
+ execute: async (args) => {
230
+ assertNoExtraArgs("workflows intro", args);
231
+ writeJson({ intro: await loadNornWorkflowsIntro(process.cwd()) });
232
+ },
233
+ },
218
234
  {
219
235
  id: "workflows.inspect",
220
236
  path: ["workflows", "inspect"],
@@ -485,6 +501,7 @@ const HELP_COMMAND_ORDER = [
485
501
  "docs.inspect",
486
502
  "docs.intro",
487
503
  "workflows.list",
504
+ "workflows.intro",
488
505
  "workflows.inspect",
489
506
  "runs.start",
490
507
  "runs.resume",
@@ -513,6 +530,8 @@ const HUMAN_COMMAND_SUMMARIES: Readonly<Record<string, string>> = {
513
530
  "docs.intro":
514
531
  "Produce a compact authoring introduction with local documentation pointers.",
515
532
  "workflows.list": "List Norn workflows.",
533
+ "workflows.intro":
534
+ "Advertise current entrypoints with caller instructions as XML.",
516
535
  "workflows.inspect": "Inspect a workflow schema and source.",
517
536
  "runs.start": "Start a workflow run.",
518
537
  "runs.resume": "Resume an interrupted gate or a restored checkpoint.",
@@ -2,8 +2,8 @@ import type { NornBuildInfo } from "./build-info.ts";
2
2
 
3
3
  export const NORN_GENERATED_BUILD_INFO = {
4
4
  "kind": "npm-registry",
5
- "version": "0.1.0-tip.35725822562.1",
6
- "commit": "95e10f8b64ae8220d24420c3b09484f7e7943c3f",
5
+ "version": "0.1.0-tip.36148588553.1",
6
+ "commit": "bef15bd5452b3202f93f33f0b33bdf07a48f7ffc",
7
7
  "packageSpec": "@vimhead.dev/norn-cli@tip",
8
8
  "upgrade": {
9
9
  "supported": false,
@@ -1,6 +1,13 @@
1
1
  import type { NornWorkflowDiagnostic } from "@vimhead.dev/norn";
2
2
  import { AssertError } from "typebox/value";
3
3
 
4
+ export class NornProjectNotFoundError extends Error {
5
+ constructor(message: string) {
6
+ super(message);
7
+ this.name = "NornProjectNotFoundError";
8
+ }
9
+ }
10
+
4
11
  export class NornProjectLoadError extends Error {
5
12
  readonly code = "NORN_PROJECT_INVALID";
6
13
  readonly isComplete = false;
@@ -24,7 +24,11 @@ import * as typeboxCompileModule from "typebox/compile";
24
24
  import * as typeboxSchemaModule from "typebox/schema";
25
25
  import * as typeboxValueModule from "typebox/value";
26
26
  import { AssertError, Value } from "typebox/value";
27
- import { errorMessage, NornProjectLoadError } from "./internal/errors.ts";
27
+ import {
28
+ errorMessage,
29
+ NornProjectLoadError,
30
+ NornProjectNotFoundError,
31
+ } from "./internal/errors.ts";
28
32
  import {
29
33
  assertCompatibleConfigurationOwners,
30
34
  assertWorkflowDefinition,
@@ -169,7 +173,9 @@ async function findNearestNornProject(cwd: string): Promise<string> {
169
173
  }
170
174
  const parent = dirname(current);
171
175
  if (parent === current)
172
- throw new Error(`Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`);
176
+ throw new NornProjectNotFoundError(
177
+ `Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`,
178
+ );
173
179
  current = parent;
174
180
  }
175
181
  }
@@ -0,0 +1,43 @@
1
+ import type { NornRegisteredWorkflowInfo } from "@vimhead.dev/norn";
2
+ import { NornProjectNotFoundError } from "./internal/errors.ts";
3
+ import { loadNornProject } from "./workflow-loader.ts";
4
+
5
+ export async function loadNornWorkflowsIntro(cwd: string): Promise<string> {
6
+ try {
7
+ const project = await loadNornProject(cwd);
8
+ return renderNornWorkflowsIntro(project.registry.list());
9
+ } catch (error) {
10
+ if (error instanceof NornProjectNotFoundError) return "";
11
+ throw error;
12
+ }
13
+ }
14
+
15
+ export function renderNornWorkflowsIntro(
16
+ workflows: readonly NornRegisteredWorkflowInfo[],
17
+ ): string {
18
+ const entrypoints = workflows.filter((workflow) => workflow.isEntrypoint);
19
+ if (entrypoints.length === 0) return "";
20
+ return [
21
+ "The following Norn workflows are available in the current project.",
22
+ "When a task matches, use `workflows inspect <id>` with the runtime specified above to load its current contract.",
23
+ "Use `workflows list` to refresh this snapshot after workflow changes.",
24
+ "",
25
+ "<available_norn_workflows>",
26
+ ...entrypoints.flatMap((workflow) => [
27
+ " <workflow>",
28
+ ` <id>${escapeXml(workflow.id)}</id>`,
29
+ ` <instructions>${escapeXml(workflow.instructions ?? "")}</instructions>`,
30
+ " </workflow>",
31
+ ]),
32
+ "</available_norn_workflows>",
33
+ ].join("\n");
34
+ }
35
+
36
+ function escapeXml(value: string): string {
37
+ return value
38
+ .replaceAll("&", "&amp;")
39
+ .replaceAll("<", "&lt;")
40
+ .replaceAll(">", "&gt;")
41
+ .replaceAll('"', "&quot;")
42
+ .replaceAll("'", "&apos;");
43
+ }
@@ -0,0 +1,36 @@
1
+ import { execFile } from "node:child_process";
2
+ import { Buffer } from "node:buffer";
3
+ import { promisify } from "node:util";
4
+
5
+ const runExecutable = promisify(execFile);
6
+ const MAX_INTRO_BYTES = 16_384;
7
+
8
+ function readIntroResponse({ stdout, group }) {
9
+ const response = JSON.parse(stdout);
10
+ if (
11
+ !response ||
12
+ typeof response !== "object" ||
13
+ typeof response.intro !== "string" ||
14
+ (group === "docs" && response.intro.trim().length === 0) ||
15
+ Buffer.byteLength(response.intro, "utf8") > MAX_INTRO_BYTES
16
+ )
17
+ throw new Error("Invalid Norn introduction response");
18
+ return response.intro;
19
+ }
20
+
21
+ async function loadIntroduction({ executable, cwd, group }) {
22
+ const { stdout } = await runExecutable(executable, [group, "intro"], {
23
+ cwd,
24
+ timeout: 10_000,
25
+ maxBuffer: MAX_INTRO_BYTES * 2,
26
+ });
27
+ return readIntroResponse({ stdout, group });
28
+ }
29
+
30
+ export async function loadHostIntroduction({ executable, cwd }) {
31
+ const [intro, workflowsIntro] = await Promise.all([
32
+ loadIntroduction({ executable, cwd, group: "docs" }),
33
+ loadIntroduction({ executable, cwd, group: "workflows" }),
34
+ ]);
35
+ return `<norn-docs-intro>\n${intro}\n</norn-docs-intro>${workflowsIntro ? `\n\n${workflowsIntro}` : ""}`;
36
+ }
package/dist/cli.js CHANGED
@@ -64,6 +64,7 @@ import {
64
64
  loadNornProject,
65
65
  NORN_PROJECT_FILE_NAME
66
66
  } from "./workflow-loader.js";
67
+ import { loadNornWorkflowsIntro } from "./workflows-intro.js";
67
68
  var RUNS_ROOT = join(".norn", "runs");
68
69
  var RUN_WAIT_INTERVAL_MS = 1e3;
69
70
  var CLI_DESCRIPTION = "Norn is a harness-agnostic runtime for agent-driven and code-driven workflows, built primarily for agents. Build workflows with the Norn SDK; use this JSON-native CLI to discover workflows, start or resume runs, inspect evidence, and manage the installed runtime.";
@@ -193,6 +194,18 @@ var COMMANDS = [
193
194
  examples: ["norn workflows list", "norn workflows list --all"],
194
195
  execute: listWorkflows
195
196
  },
197
+ {
198
+ id: "workflows.intro",
199
+ path: ["workflows", "intro"],
200
+ description: "Produce caller guidance and an XML advertisement of the current project's entrypoint workflows. Loads project modules but does not execute workflows or deliver context to agents.",
201
+ usage: "norn workflows intro",
202
+ output: "JSON object with text under intro. Empty when no project or entrypoints exist; project-loading failures are errors, not partial advertisements.",
203
+ examples: ["norn workflows intro"],
204
+ execute: async (args) => {
205
+ assertNoExtraArgs("workflows intro", args);
206
+ writeJson({ intro: await loadNornWorkflowsIntro(process.cwd()) });
207
+ }
208
+ },
196
209
  {
197
210
  id: "workflows.inspect",
198
211
  path: ["workflows", "inspect"],
@@ -441,6 +454,7 @@ var HELP_COMMAND_ORDER = [
441
454
  "docs.inspect",
442
455
  "docs.intro",
443
456
  "workflows.list",
457
+ "workflows.intro",
444
458
  "workflows.inspect",
445
459
  "runs.start",
446
460
  "runs.resume",
@@ -467,6 +481,7 @@ var HUMAN_COMMAND_SUMMARIES = {
467
481
  "docs.inspect": "Locate matching documentation and examples offline.",
468
482
  "docs.intro": "Produce a compact authoring introduction with local documentation pointers.",
469
483
  "workflows.list": "List Norn workflows.",
484
+ "workflows.intro": "Advertise current entrypoints with caller instructions as XML.",
470
485
  "workflows.inspect": "Inspect a workflow schema and source.",
471
486
  "runs.start": "Start a workflow run.",
472
487
  "runs.resume": "Resume an interrupted gate or a restored checkpoint.",
@@ -1,7 +1,7 @@
1
1
  export declare const NORN_GENERATED_BUILD_INFO: {
2
2
  readonly kind: "npm-registry";
3
- readonly version: "0.1.0-tip.35725822562.1";
4
- readonly commit: "95e10f8b64ae8220d24420c3b09484f7e7943c3f";
3
+ readonly version: "0.1.0-tip.36148588553.1";
4
+ readonly commit: "bef15bd5452b3202f93f33f0b33bdf07a48f7ffc";
5
5
  readonly packageSpec: "@vimhead.dev/norn-cli@tip";
6
6
  readonly upgrade: {
7
7
  readonly supported: false;
@@ -1,8 +1,8 @@
1
1
  // src/generated-build-info.ts
2
2
  var NORN_GENERATED_BUILD_INFO = {
3
3
  "kind": "npm-registry",
4
- "version": "0.1.0-tip.35725822562.1",
5
- "commit": "95e10f8b64ae8220d24420c3b09484f7e7943c3f",
4
+ "version": "0.1.0-tip.36148588553.1",
5
+ "commit": "bef15bd5452b3202f93f33f0b33bdf07a48f7ffc",
6
6
  "packageSpec": "@vimhead.dev/norn-cli@tip",
7
7
  "upgrade": {
8
8
  "supported": false,
@@ -1,5 +1,8 @@
1
1
  import type { NornWorkflowDiagnostic } from "@vimhead.dev/norn";
2
2
  import { AssertError } from "typebox/value";
3
+ export declare class NornProjectNotFoundError extends Error {
4
+ constructor(message: string);
5
+ }
3
6
  export declare class NornProjectLoadError extends Error {
4
7
  readonly code = "NORN_PROJECT_INVALID";
5
8
  readonly isComplete = false;
@@ -1,5 +1,11 @@
1
1
  // src/internal/errors.ts
2
2
  import { AssertError } from "typebox/value";
3
+ var NornProjectNotFoundError = class extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "NornProjectNotFoundError";
7
+ }
8
+ };
3
9
  var NornProjectLoadError = class extends Error {
4
10
  code = "NORN_PROJECT_INVALID";
5
11
  isComplete = false;
@@ -31,6 +37,7 @@ function errorMessage(error) {
31
37
  }
32
38
  export {
33
39
  NornProjectLoadError,
40
+ NornProjectNotFoundError,
34
41
  NornRunStoppedError,
35
42
  errorMessage,
36
43
  schemaErrorMessage
@@ -22,7 +22,11 @@ import * as typeboxCompileModule from "typebox/compile";
22
22
  import * as typeboxSchemaModule from "typebox/schema";
23
23
  import * as typeboxValueModule from "typebox/value";
24
24
  import { AssertError, Value } from "typebox/value";
25
- import { errorMessage, NornProjectLoadError } from "./internal/errors.js";
25
+ import {
26
+ errorMessage,
27
+ NornProjectLoadError,
28
+ NornProjectNotFoundError
29
+ } from "./internal/errors.js";
26
30
  import {
27
31
  assertCompatibleConfigurationOwners,
28
32
  assertWorkflowDefinition,
@@ -126,7 +130,9 @@ async function findNearestNornProject(cwd) {
126
130
  }
127
131
  const parent = dirname(current);
128
132
  if (parent === current)
129
- throw new Error(`Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`);
133
+ throw new NornProjectNotFoundError(
134
+ `Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`
135
+ );
130
136
  current = parent;
131
137
  }
132
138
  }
@@ -0,0 +1,3 @@
1
+ import type { NornRegisteredWorkflowInfo } from "@vimhead.dev/norn";
2
+ export declare function loadNornWorkflowsIntro(cwd: string): Promise<string>;
3
+ export declare function renderNornWorkflowsIntro(workflows: readonly NornRegisteredWorkflowInfo[]): string;
@@ -0,0 +1,37 @@
1
+ // src/workflows-intro.ts
2
+ import { NornProjectNotFoundError } from "./internal/errors.js";
3
+ import { loadNornProject } from "./workflow-loader.js";
4
+ async function loadNornWorkflowsIntro(cwd) {
5
+ try {
6
+ const project = await loadNornProject(cwd);
7
+ return renderNornWorkflowsIntro(project.registry.list());
8
+ } catch (error) {
9
+ if (error instanceof NornProjectNotFoundError) return "";
10
+ throw error;
11
+ }
12
+ }
13
+ function renderNornWorkflowsIntro(workflows) {
14
+ const entrypoints = workflows.filter((workflow) => workflow.isEntrypoint);
15
+ if (entrypoints.length === 0) return "";
16
+ return [
17
+ "The following Norn workflows are available in the current project.",
18
+ "When a task matches, use `workflows inspect <id>` with the runtime specified above to load its current contract.",
19
+ "Use `workflows list` to refresh this snapshot after workflow changes.",
20
+ "",
21
+ "<available_norn_workflows>",
22
+ ...entrypoints.flatMap((workflow) => [
23
+ " <workflow>",
24
+ ` <id>${escapeXml(workflow.id)}</id>`,
25
+ ` <instructions>${escapeXml(workflow.instructions ?? "")}</instructions>`,
26
+ " </workflow>"
27
+ ]),
28
+ "</available_norn_workflows>"
29
+ ].join("\n");
30
+ }
31
+ function escapeXml(value) {
32
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
33
+ }
34
+ export {
35
+ loadNornWorkflowsIntro,
36
+ renderNornWorkflowsIntro
37
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vimhead.dev/norn-cli",
3
- "version": "0.1.0-tip.35725822562.1",
3
+ "version": "0.1.0-tip.36148588553.1",
4
4
  "description": "Harness-agnostic runtime for agent-driven and code-driven workflows, built primarily for agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "@earendil-works/pi-coding-agent": "0.85.1",
44
44
  "jiti": "^2.7.0",
45
45
  "typebox": "^1.3.34",
46
- "@vimhead.dev/norn": "0.1.0-tip.35725822562.1"
46
+ "@vimhead.dev/norn": "0.1.0-tip.36148588553.1"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "node ../../scripts/build-package.ts"