@nawc/cli 0.1.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/dist/cli.mjs ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ import { n as assertGitRepository, t as createNawcServer } from "./server-B0NGps95.mjs";
3
+ import path from "node:path";
4
+ import { readFile } from "node:fs/promises";
5
+ import { createJiti } from "jiti";
6
+ import { execFile } from "node:child_process";
7
+ import { promisify } from "node:util";
8
+ import { parseFragment } from "parse5";
9
+ import { Command } from "commander";
10
+ //#region src/splash.ts
11
+ const execFileAsync = promisify(execFile);
12
+ function isElement(node) {
13
+ return "tagName" in node && Array.isArray(node.attrs);
14
+ }
15
+ function notePathFor(target) {
16
+ return target.endsWith(".html") ? target : `${target}.html`;
17
+ }
18
+ function normalizeWikiLinkTarget(target) {
19
+ return notePathFor(target.trim());
20
+ }
21
+ function extractWikiLinks(html) {
22
+ const root = parseFragment(html);
23
+ const seen = /* @__PURE__ */ new Set();
24
+ const visit = (node) => {
25
+ if (isElement(node)) {
26
+ if (node.nodeName === "a") {
27
+ const explicit = node.attrs.find((attr) => attr.name === "data-wiki-link")?.value.trim();
28
+ if (explicit) seen.add(normalizeWikiLinkTarget(explicit));
29
+ else {
30
+ const match = node.childNodes.map((child) => "value" in child ? child.value ?? "" : "").join("").trim().match(/^\[\[([^\]]+)\]\]$/);
31
+ if (match) seen.add(normalizeWikiLinkTarget(match[1]));
32
+ }
33
+ }
34
+ }
35
+ const children = "childNodes" in node ? node.childNodes : [];
36
+ for (const child of children) visit(child);
37
+ };
38
+ visit(root);
39
+ return [...seen].sort();
40
+ }
41
+ function extractFileReferences(html, plugins) {
42
+ const seen = /* @__PURE__ */ new Set();
43
+ for (const plugin of plugins) {
44
+ if (!plugin.references) continue;
45
+ for (const ref of plugin.references({ html })) {
46
+ const trimmed = ref.path.trim();
47
+ if (trimmed) seen.add(trimmed);
48
+ }
49
+ }
50
+ return [...seen].sort();
51
+ }
52
+ const MERGE_CONFLICT_STATUSES = /* @__PURE__ */ new Set([
53
+ "UU",
54
+ "AA",
55
+ "DD",
56
+ "AU",
57
+ "UA",
58
+ "UD",
59
+ "DU"
60
+ ]);
61
+ /**
62
+ * Returns project files modified in the working tree relative to the index,
63
+ * including untracked files but excluding ignored and merge-conflict entries.
64
+ */
65
+ async function getModifiedProjectFiles(baseDir) {
66
+ const { stdout } = await execFileAsync("git", [
67
+ "-C",
68
+ baseDir,
69
+ "status",
70
+ "--porcelain",
71
+ "--untracked-files=all"
72
+ ], {
73
+ encoding: "utf8",
74
+ maxBuffer: 16 * 1024 * 1024
75
+ });
76
+ const files = /* @__PURE__ */ new Set();
77
+ for (const line of stdout.split("\n")) {
78
+ if (line.length < 4) continue;
79
+ const status = line.slice(0, 2);
80
+ if (status === "!!") continue;
81
+ if (MERGE_CONFLICT_STATUSES.has(status)) continue;
82
+ const rest = line.slice(3);
83
+ if (status.startsWith("R") || status.startsWith("C")) {
84
+ const match = rest.match(/^(.+) -> (.+)$/);
85
+ if (match) files.add(match[2].trim());
86
+ } else files.add(rest.trim());
87
+ }
88
+ return files;
89
+ }
90
+ async function readNoteFile(srcDir, note) {
91
+ return readFile(path.join(srcDir, note), "utf8");
92
+ }
93
+ async function computeSplash(inputs) {
94
+ const depth = Math.max(0, Math.floor(inputs.depth));
95
+ const modifiedFiles = await getModifiedProjectFiles(inputs.baseDir);
96
+ const notePaths = await readDirectory(inputs.srcDir);
97
+ const zone = [];
98
+ const cache = /* @__PURE__ */ new Map();
99
+ for (const note of notePaths) {
100
+ const html = await readNoteFile(inputs.srcDir, note);
101
+ cache.set(note, html);
102
+ if (extractFileReferences(html, inputs.plugins).some((ref) => modifiedFiles.has(ref))) zone.push(note);
103
+ }
104
+ zone.sort();
105
+ const layers = [];
106
+ let frontier = new Set(zone);
107
+ const visited = new Set(zone);
108
+ for (let current = 1; current <= depth; current += 1) {
109
+ const next = /* @__PURE__ */ new Set();
110
+ const links = [];
111
+ for (const source of [...frontier].sort()) {
112
+ const html = cache.get(source) ?? await readNoteFile(inputs.srcDir, source);
113
+ cache.set(source, html);
114
+ const targets = extractWikiLinks(html);
115
+ if (targets.length === 0) continue;
116
+ links.push({
117
+ source,
118
+ targets
119
+ });
120
+ for (const target of targets) if (!visited.has(target)) {
121
+ visited.add(target);
122
+ next.add(target);
123
+ }
124
+ }
125
+ if (links.length === 0) break;
126
+ layers.push({
127
+ depth: current,
128
+ links
129
+ });
130
+ if (next.size === 0) break;
131
+ frontier = next;
132
+ }
133
+ return {
134
+ zone,
135
+ layers,
136
+ modifiedFiles: [...modifiedFiles].sort()
137
+ };
138
+ }
139
+ async function readDirectory(directory) {
140
+ const { readdir } = await import("node:fs/promises");
141
+ const notes = [];
142
+ const walk = async (dir) => {
143
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
144
+ const file = path.join(dir, entry.name);
145
+ if (entry.isDirectory()) await walk(file);
146
+ else if (entry.isFile() && entry.name.endsWith(".html")) notes.push(path.relative(directory, file));
147
+ }
148
+ };
149
+ await walk(directory);
150
+ return notes.sort();
151
+ }
152
+ //#endregion
153
+ //#region src/cli.ts
154
+ async function loadConfig(projectDir, configFile) {
155
+ const file = path.resolve(projectDir, configFile);
156
+ return createJiti(import.meta.url, { interopDefault: true }).import(file, { default: true });
157
+ }
158
+ function formatSplash(result) {
159
+ const lines = [];
160
+ lines.push("# Splash Zone");
161
+ if (result.zone.length === 0) lines.push("- (none)");
162
+ else for (const note of result.zone) lines.push(`- ${note}`);
163
+ for (const layer of result.layers) {
164
+ if (layer.links.length === 0) continue;
165
+ lines.push("");
166
+ lines.push(`# Related - Depth ${layer.depth}`);
167
+ for (const link of layer.links) lines.push(`- ${link.source} references ${link.targets.join(", ")}`);
168
+ }
169
+ return `${lines.join("\n")}\n`;
170
+ }
171
+ async function runSplash(projectDir, options) {
172
+ const config = await loadConfig(projectDir, options.config);
173
+ const baseDir = path.resolve(projectDir, config.baseDir);
174
+ await assertGitRepository(baseDir);
175
+ const result = await computeSplash({
176
+ srcDir: path.join(projectDir, "src"),
177
+ baseDir,
178
+ plugins: config.plugins,
179
+ depth: options.depth
180
+ });
181
+ process.stdout.write(formatSplash(result));
182
+ }
183
+ const program = new Command().name("nawc").description("Start a NAWC notebook").option("-p, --port <port>", "port to listen on", Number).option("-H, --host <host>", "host/interface to bind to").option("-c, --config <file>", "configuration file", "nawc.config.ts").action(async (options) => {
184
+ const running = await createNawcServer({
185
+ projectDir: process.cwd(),
186
+ configFile: options.config,
187
+ port: options.port,
188
+ host: options.host
189
+ });
190
+ console.log(`NAWC is ready at ${running.url}`);
191
+ const close = async () => {
192
+ await running.close();
193
+ process.exit(0);
194
+ };
195
+ process.once("SIGINT", () => void close());
196
+ process.once("SIGTERM", () => void close());
197
+ });
198
+ program.command("splash").description("List notes whose referenced files are modified in the worktree").option("-d, --depth <depth>", "how many wiki-link hops to follow", (value) => {
199
+ const parsed = Number(value);
200
+ if (!Number.isInteger(parsed) || parsed < 0) throw new Error("Depth must be a non-negative integer");
201
+ return parsed;
202
+ }, 0).option("-c, --config <file>", "configuration file", "nawc.config.ts").action(async (options) => {
203
+ await runSplash(process.cwd(), options);
204
+ });
205
+ program.parseAsync().catch((error) => {
206
+ console.error(error instanceof Error ? error.message : error);
207
+ process.exit(1);
208
+ });
209
+ //#endregion
210
+ export {};
@@ -0,0 +1,26 @@
1
+ import { EditorLocation, EditorTarget, NawcAgentAttachment, NawcConfig, NawcConfig as NawcConfig$1, NawcEditor, NawcEditor as NawcEditor$1, NawcProvider, NawcProviderCapability, NawcProviderMode, NawcProviderModel, NawcProviderOption, NawcProviderOptionSelection, NawcProviderReasoningEffort, NawcProviderSession, NawcProviderSettings, NawcProviderSkill, NawcProviderSlashCommand, NawcSyntax, NawcTheme, NawcTheme as NawcTheme$1 } from "@nawc/config";
2
+ import { nawcDark, nawcLight } from "@nawc/theme-nawc";
3
+ import { Server } from "node:http";
4
+ import { ViteDevServer } from "vite";
5
+ //#region src/define-config.d.ts
6
+ declare function defineConfig<const T extends NawcConfig$1>(config: T): T & {
7
+ readonly editor: NawcEditor$1;
8
+ readonly theme: NawcTheme$1;
9
+ };
10
+ //#endregion
11
+ //#region src/server.d.ts
12
+ type ServerOptions = {
13
+ readonly projectDir: string;
14
+ readonly configFile?: string;
15
+ readonly port?: number;
16
+ readonly host?: string;
17
+ };
18
+ type RunningServer = {
19
+ readonly server: Server;
20
+ readonly vite: ViteDevServer;
21
+ readonly url: string;
22
+ close(): Promise<void>;
23
+ };
24
+ declare function createNawcServer(options: ServerOptions): Promise<RunningServer>;
25
+ //#endregion
26
+ export { type EditorLocation, type EditorTarget, type NawcAgentAttachment, type NawcConfig, type NawcEditor, type NawcProvider, type NawcProviderCapability, type NawcProviderMode, type NawcProviderModel, type NawcProviderOption, type NawcProviderOptionSelection, type NawcProviderReasoningEffort, type NawcProviderSession, type NawcProviderSettings, type NawcProviderSkill, type NawcProviderSlashCommand, type NawcSyntax, type NawcTheme, createNawcServer, defineConfig, nawcDark, nawcLight };
package/dist/index.mjs ADDED
@@ -0,0 +1,15 @@
1
+ import { t as createNawcServer } from "./server-B0NGps95.mjs";
2
+ import { configShape } from "@nawc/config";
3
+ import { vscode } from "@nawc/editor-vscode";
4
+ import { nawcDark, nawcLight, nawcLight as nawcLight$1 } from "@nawc/theme-nawc";
5
+ //#region src/define-config.ts
6
+ function defineConfig(config) {
7
+ configShape.parse(config);
8
+ return {
9
+ ...config,
10
+ editor: config.editor ?? vscode(),
11
+ theme: config.theme ?? nawcLight$1()
12
+ };
13
+ }
14
+ //#endregion
15
+ export { createNawcServer, defineConfig, nawcDark, nawcLight };