@abaigram/abaikit 0.0.0-stage → 0.2.0
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 +21 -0
- package/README.md +15 -2
- package/abai/identity.md +1 -0
- package/abai/learnabai.md +546 -0
- package/abai/use.md +1 -0
- package/abaigram.yaml +208 -0
- package/dist/abaify.d.ts +68 -0
- package/dist/abaify.d.ts.map +1 -0
- package/dist/abaify.js +310 -0
- package/dist/abaify.js.map +1 -0
- package/dist/abaigram.d.ts +18 -0
- package/dist/abaigram.d.ts.map +1 -0
- package/dist/abaigram.js +153 -0
- package/dist/abaigram.js.map +1 -0
- package/dist/app.d.ts +19 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +29 -0
- package/dist/app.js.map +1 -0
- package/dist/bin.d.ts +46 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +274 -0
- package/dist/bin.js.map +1 -0
- package/dist/check.d.ts +119 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +416 -0
- package/dist/check.js.map +1 -0
- package/dist/command.d.ts +41 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +179 -0
- package/dist/command.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +12 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +20 -0
- package/dist/io.js.map +1 -0
- package/dist/learnabai.d.ts +60 -0
- package/dist/learnabai.d.ts.map +1 -0
- package/dist/learnabai.js +199 -0
- package/dist/learnabai.js.map +1 -0
- package/dist/new.d.ts +26 -0
- package/dist/new.d.ts.map +1 -0
- package/dist/new.js +87 -0
- package/dist/new.js.map +1 -0
- package/dist/repo.d.ts +99 -0
- package/dist/repo.d.ts.map +1 -0
- package/dist/repo.js +482 -0
- package/dist/repo.js.map +1 -0
- package/dist/run.d.ts +6 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +47 -0
- package/dist/run.js.map +1 -0
- package/dist/subject.d.ts +15 -0
- package/dist/subject.d.ts.map +1 -0
- package/dist/subject.js +53 -0
- package/dist/subject.js.map +1 -0
- package/dist/template-ts.d.ts +21 -0
- package/dist/template-ts.d.ts.map +1 -0
- package/dist/template-ts.js +322 -0
- package/dist/template-ts.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/examples/hello/abai/identity.md +1 -0
- package/examples/hello/abaigram.yaml +24 -0
- package/examples/hello/hello.ts +18 -0
- package/package.json +40 -5
package/dist/command.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Running a subject's commands: its entry's verbs and its check.run, by
|
|
2
|
+
// the shell in its root, with a constructed environment and a bound on
|
|
3
|
+
// time. The command runs in a process group of its own, so a compound
|
|
4
|
+
// command's children are ended with it: a run that outlives its bound is
|
|
5
|
+
// ended by that group, SIGTERM then SIGKILL, and abaikit's own end ends
|
|
6
|
+
// it too. Nothing on the machine is looked for by name.
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { performance } from "node:perf_hooks";
|
|
9
|
+
/** Variables that tell the stub what to do; a subject inherits only where things are (`ABAI_HOME`, `ABAI_RUN_DIR`, the harness locations). */
|
|
10
|
+
export const BEHAVIOUR_VARIABLES = [
|
|
11
|
+
"ABAI_MODE",
|
|
12
|
+
"ABAI_HARNESS",
|
|
13
|
+
"ABAI_LAUNCH_SOCKET",
|
|
14
|
+
"ABAI_CHANNEL",
|
|
15
|
+
"ABAI_ATTACH_WAIT_MS",
|
|
16
|
+
];
|
|
17
|
+
/** The environment a subject's run gets: ours without the behaviour variables, plus exactly what this run means. */
|
|
18
|
+
export function subjectEnv(base, set = {}) {
|
|
19
|
+
const env = {};
|
|
20
|
+
for (const [key, value] of Object.entries(base)) {
|
|
21
|
+
if (!BEHAVIOUR_VARIABLES.includes(key))
|
|
22
|
+
env[key] = value;
|
|
23
|
+
}
|
|
24
|
+
return { ...env, ...set };
|
|
25
|
+
}
|
|
26
|
+
/** After the timeout's SIGTERM, how long before the group is killed. */
|
|
27
|
+
export const FORCE_AFTER_MS = 2000;
|
|
28
|
+
/** After the shell has exited, how long a survivor may hold the pipes before the group is killed and the run resolves. */
|
|
29
|
+
export const CLOSE_GRACE_MS = 1500;
|
|
30
|
+
/** The newest `max` bytes of a stream, as chunks arrive. */
|
|
31
|
+
class Tail {
|
|
32
|
+
chunks = [];
|
|
33
|
+
max;
|
|
34
|
+
kept = 0;
|
|
35
|
+
dropped = 0;
|
|
36
|
+
constructor(max) {
|
|
37
|
+
this.max = max;
|
|
38
|
+
}
|
|
39
|
+
push(chunk) {
|
|
40
|
+
this.chunks.push(chunk);
|
|
41
|
+
this.kept += chunk.length;
|
|
42
|
+
while (this.kept > this.max && this.chunks.length > 1) {
|
|
43
|
+
const oldest = this.chunks.shift();
|
|
44
|
+
this.kept -= oldest.length;
|
|
45
|
+
this.dropped += oldest.length;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
text() {
|
|
49
|
+
return Buffer.concat(this.chunks).toString("utf8");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function runCommand(command, options) {
|
|
53
|
+
const max = options.maxOutputBytes ?? 4 * 1024 * 1024;
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const started = performance.now();
|
|
56
|
+
const child = spawn("sh", ["-c", command], {
|
|
57
|
+
cwd: options.cwd,
|
|
58
|
+
env: options.env,
|
|
59
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
60
|
+
detached: true,
|
|
61
|
+
});
|
|
62
|
+
const out = new Tail(max);
|
|
63
|
+
const err = new Tail(max);
|
|
64
|
+
child.stdout?.on("data", (chunk) => out.push(chunk));
|
|
65
|
+
child.stderr?.on("data", (chunk) => err.push(chunk));
|
|
66
|
+
// the whole group, by the pid we were given; the child alone when the group is already gone
|
|
67
|
+
const signalGroup = (signal) => {
|
|
68
|
+
const pid = child.pid;
|
|
69
|
+
if (pid === undefined)
|
|
70
|
+
return;
|
|
71
|
+
try {
|
|
72
|
+
process.kill(-pid, signal);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
try {
|
|
76
|
+
child.kill(signal);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// already gone
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
let timedOut = false;
|
|
84
|
+
let settled = false;
|
|
85
|
+
let exited;
|
|
86
|
+
let closeGrace;
|
|
87
|
+
const timer = setTimeout(() => {
|
|
88
|
+
timedOut = true;
|
|
89
|
+
signalGroup("SIGTERM");
|
|
90
|
+
}, options.timeoutMs);
|
|
91
|
+
const force = setTimeout(() => signalGroup("SIGKILL"), options.timeoutMs + FORCE_AFTER_MS);
|
|
92
|
+
force.unref();
|
|
93
|
+
// a group of its own is outside the terminal's foreground group: our end has to reach it
|
|
94
|
+
const forward = (signal) => signalGroup(signal);
|
|
95
|
+
const forwarded = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
96
|
+
for (const signal of forwarded)
|
|
97
|
+
process.on(signal, forward);
|
|
98
|
+
const finish = (code, signal, failure) => {
|
|
99
|
+
if (settled)
|
|
100
|
+
return;
|
|
101
|
+
settled = true;
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
clearTimeout(force);
|
|
104
|
+
if (closeGrace !== undefined)
|
|
105
|
+
clearTimeout(closeGrace);
|
|
106
|
+
for (const s of forwarded)
|
|
107
|
+
process.off(s, forward);
|
|
108
|
+
resolve({
|
|
109
|
+
command,
|
|
110
|
+
code,
|
|
111
|
+
signal,
|
|
112
|
+
stdout: out.text(),
|
|
113
|
+
stderr: err.text() + (failure ? `${failure.message}\n` : ""),
|
|
114
|
+
dropped: { stdout: out.dropped, stderr: err.dropped },
|
|
115
|
+
ms: performance.now() - started,
|
|
116
|
+
timedOut,
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
child.once("error", (error) => finish(null, null, error));
|
|
120
|
+
child.once("exit", (code, signal) => {
|
|
121
|
+
exited = { code, signal };
|
|
122
|
+
// the pipes may be held by a survivor of the group: a bounded wait for close, then the group is ended
|
|
123
|
+
closeGrace = setTimeout(() => {
|
|
124
|
+
signalGroup("SIGKILL");
|
|
125
|
+
finish(code, signal);
|
|
126
|
+
}, CLOSE_GRACE_MS);
|
|
127
|
+
});
|
|
128
|
+
child.once("close", (code, signal) => finish(exited?.code ?? code, exited?.signal ?? signal));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** A stream that is one JSON object, pretty or not, else its last line that is one. */
|
|
132
|
+
export function jsonDocument(text) {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(text);
|
|
135
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed))
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// not one document
|
|
140
|
+
}
|
|
141
|
+
return lastJsonLine(text);
|
|
142
|
+
}
|
|
143
|
+
/** The last line of a stream that parses as a JSON object and satisfies `accept`, or undefined. */
|
|
144
|
+
export function lastJsonLine(text, accept = () => true) {
|
|
145
|
+
const lines = text.split("\n").filter((l) => l.trim() !== "");
|
|
146
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
147
|
+
const line = lines[i].trim();
|
|
148
|
+
if (!line.startsWith("{"))
|
|
149
|
+
continue;
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(line);
|
|
152
|
+
if (typeof parsed === "object" &&
|
|
153
|
+
parsed !== null &&
|
|
154
|
+
!Array.isArray(parsed) &&
|
|
155
|
+
accept(parsed)) {
|
|
156
|
+
return parsed;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// not this line
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
/** The last non-empty line that is not a JSON object, for saying how a run failed. */
|
|
166
|
+
export function lastPlainLine(text) {
|
|
167
|
+
const lines = text.split("\n").filter((l) => l.trim() !== "" && !l.trim().startsWith("{"));
|
|
168
|
+
return lines.at(-1)?.trim() ?? "";
|
|
169
|
+
}
|
|
170
|
+
/** The newest `bytes` of a text, for a report that should not carry a whole log. */
|
|
171
|
+
export function tailOf(text, bytes) {
|
|
172
|
+
return text.length <= bytes ? text : `[${text.length - bytes} earlier characters omitted]\n${text.slice(-bytes)}`;
|
|
173
|
+
}
|
|
174
|
+
export function median(values) {
|
|
175
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
176
|
+
const mid = Math.floor(sorted.length / 2);
|
|
177
|
+
return sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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","sourcesContent":["// Running a subject's commands: its entry's verbs and its check.run, by\n// the shell in its root, with a constructed environment and a bound on\n// time. The command runs in a process group of its own, so a compound\n// command's children are ended with it: a run that outlives its bound is\n// ended by that group, SIGTERM then SIGKILL, and abaikit's own end ends\n// it too. Nothing on the machine is looked for by name.\nimport { spawn } from \"node:child_process\";\nimport { performance } from \"node:perf_hooks\";\n\nexport interface CommandRun {\n\tcommand: string;\n\tcode: number | null;\n\tsignal: NodeJS.Signals | null;\n\t/** The last `maxOutputBytes` of each stream; what came before is counted in `dropped`. */\n\tstdout: string;\n\tstderr: string;\n\tdropped: { stdout: number; stderr: number };\n\t/** Wall clock, from spawn to exit. */\n\tms: number;\n\ttimedOut: boolean;\n}\n\nexport interface CommandOptions {\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n\ttimeoutMs: number;\n\t/** Bytes of each stream kept, the newest; default 4 MiB. */\n\tmaxOutputBytes?: number;\n}\n\n/** Variables that tell the stub what to do; a subject inherits only where things are (`ABAI_HOME`, `ABAI_RUN_DIR`, the harness locations). */\nexport const BEHAVIOUR_VARIABLES = [\n\t\"ABAI_MODE\",\n\t\"ABAI_HARNESS\",\n\t\"ABAI_LAUNCH_SOCKET\",\n\t\"ABAI_CHANNEL\",\n\t\"ABAI_ATTACH_WAIT_MS\",\n];\n\n/** The environment a subject's run gets: ours without the behaviour variables, plus exactly what this run means. */\nexport function subjectEnv(base: NodeJS.ProcessEnv, set: Record<string, string> = {}): NodeJS.ProcessEnv {\n\tconst env: NodeJS.ProcessEnv = {};\n\tfor (const [key, value] of Object.entries(base)) {\n\t\tif (!BEHAVIOUR_VARIABLES.includes(key)) env[key] = value;\n\t}\n\treturn { ...env, ...set };\n}\n\n/** After the timeout's SIGTERM, how long before the group is killed. */\nexport const FORCE_AFTER_MS = 2000;\n/** After the shell has exited, how long a survivor may hold the pipes before the group is killed and the run resolves. */\nexport const CLOSE_GRACE_MS = 1500;\n\n/** The newest `max` bytes of a stream, as chunks arrive. */\nclass Tail {\n\tprivate readonly chunks: Buffer[] = [];\n\tprivate readonly max: number;\n\tprivate kept = 0;\n\tdropped = 0;\n\tconstructor(max: number) {\n\t\tthis.max = max;\n\t}\n\tpush(chunk: Buffer): void {\n\t\tthis.chunks.push(chunk);\n\t\tthis.kept += chunk.length;\n\t\twhile (this.kept > this.max && this.chunks.length > 1) {\n\t\t\tconst oldest = this.chunks.shift() as Buffer;\n\t\t\tthis.kept -= oldest.length;\n\t\t\tthis.dropped += oldest.length;\n\t\t}\n\t}\n\ttext(): string {\n\t\treturn Buffer.concat(this.chunks).toString(\"utf8\");\n\t}\n}\n\nexport function runCommand(command: string, options: CommandOptions): Promise<CommandRun> {\n\tconst max = options.maxOutputBytes ?? 4 * 1024 * 1024;\n\treturn new Promise((resolve) => {\n\t\tconst started = performance.now();\n\t\tconst child = spawn(\"sh\", [\"-c\", command], {\n\t\t\tcwd: options.cwd,\n\t\t\tenv: options.env,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\tdetached: true,\n\t\t});\n\t\tconst out = new Tail(max);\n\t\tconst err = new Tail(max);\n\t\tchild.stdout?.on(\"data\", (chunk: Buffer) => out.push(chunk));\n\t\tchild.stderr?.on(\"data\", (chunk: Buffer) => err.push(chunk));\n\t\t// the whole group, by the pid we were given; the child alone when the group is already gone\n\t\tconst signalGroup = (signal: NodeJS.Signals): void => {\n\t\t\tconst pid = child.pid;\n\t\t\tif (pid === undefined) return;\n\t\t\ttry {\n\t\t\t\tprocess.kill(-pid, signal);\n\t\t\t} catch {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(signal);\n\t\t\t\t} catch {\n\t\t\t\t\t// already gone\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tlet timedOut = false;\n\t\tlet settled = false;\n\t\tlet exited: { code: number | null; signal: NodeJS.Signals | null } | undefined;\n\t\tlet closeGrace: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tsignalGroup(\"SIGTERM\");\n\t\t}, options.timeoutMs);\n\t\tconst force = setTimeout(() => signalGroup(\"SIGKILL\"), options.timeoutMs + FORCE_AFTER_MS);\n\t\tforce.unref();\n\t\t// a group of its own is outside the terminal's foreground group: our end has to reach it\n\t\tconst forward = (signal: NodeJS.Signals): void => signalGroup(signal);\n\t\tconst forwarded: NodeJS.Signals[] = [\"SIGINT\", \"SIGTERM\", \"SIGHUP\"];\n\t\tfor (const signal of forwarded) process.on(signal, forward);\n\t\tconst finish = (code: number | null, signal: NodeJS.Signals | null, failure?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tclearTimeout(force);\n\t\t\tif (closeGrace !== undefined) clearTimeout(closeGrace);\n\t\t\tfor (const s of forwarded) process.off(s, forward);\n\t\t\tresolve({\n\t\t\t\tcommand,\n\t\t\t\tcode,\n\t\t\t\tsignal,\n\t\t\t\tstdout: out.text(),\n\t\t\t\tstderr: err.text() + (failure ? `${failure.message}\\n` : \"\"),\n\t\t\t\tdropped: { stdout: out.dropped, stderr: err.dropped },\n\t\t\t\tms: performance.now() - started,\n\t\t\t\ttimedOut,\n\t\t\t});\n\t\t};\n\t\tchild.once(\"error\", (error) => finish(null, null, error));\n\t\tchild.once(\"exit\", (code, signal) => {\n\t\t\texited = { code, signal };\n\t\t\t// the pipes may be held by a survivor of the group: a bounded wait for close, then the group is ended\n\t\t\tcloseGrace = setTimeout(() => {\n\t\t\t\tsignalGroup(\"SIGKILL\");\n\t\t\t\tfinish(code, signal);\n\t\t\t}, CLOSE_GRACE_MS);\n\t\t});\n\t\tchild.once(\"close\", (code, signal) => finish(exited?.code ?? code, exited?.signal ?? signal));\n\t});\n}\n\n/** A stream that is one JSON object, pretty or not, else its last line that is one. */\nexport function jsonDocument(text: string): Record<string, unknown> | undefined {\n\ttry {\n\t\tconst parsed = JSON.parse(text) as unknown;\n\t\tif (typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed))\n\t\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\t// not one document\n\t}\n\treturn lastJsonLine(text);\n}\n\n/** The last line of a stream that parses as a JSON object and satisfies `accept`, or undefined. */\nexport function lastJsonLine(\n\ttext: string,\n\taccept: (value: Record<string, unknown>) => boolean = () => true,\n): Record<string, unknown> | undefined {\n\tconst lines = text.split(\"\\n\").filter((l) => l.trim() !== \"\");\n\tfor (let i = lines.length - 1; i >= 0; i--) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line.startsWith(\"{\")) continue;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(line) as unknown;\n\t\t\tif (\n\t\t\t\ttypeof parsed === \"object\" &&\n\t\t\t\tparsed !== null &&\n\t\t\t\t!Array.isArray(parsed) &&\n\t\t\t\taccept(parsed as Record<string, unknown>)\n\t\t\t) {\n\t\t\t\treturn parsed as Record<string, unknown>;\n\t\t\t}\n\t\t} catch {\n\t\t\t// not this line\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** The last non-empty line that is not a JSON object, for saying how a run failed. */\nexport function lastPlainLine(text: string): string {\n\tconst lines = text.split(\"\\n\").filter((l) => l.trim() !== \"\" && !l.trim().startsWith(\"{\"));\n\treturn lines.at(-1)?.trim() ?? \"\";\n}\n\n/** The newest `bytes` of a text, for a report that should not carry a whole log. */\nexport function tailOf(text: string, bytes: number): string {\n\treturn text.length <= bytes ? text : `[${text.length - bytes} earlier characters omitted]\\n${text.slice(-bytes)}`;\n}\n\nexport function median(values: number[]): number {\n\tconst sorted = [...values].sort((a, b) => a - b);\n\tconst mid = Math.floor(sorted.length / 2);\n\treturn sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** abaikit: the abai authoring tool, as a library and a binary. See spec/abaigrammar.md. */
|
|
2
|
+
export { type AbaikitHandlers, createAbaikit } from "./abaigram.ts";
|
|
3
|
+
export { main, type ParsedArgs, parseArgs, UsageError } from "./bin.ts";
|
|
4
|
+
export { ABAIKIT_VERSION } from "./version.ts";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAE5F,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC","sourcesContent":["/** abaikit: the abai authoring tool, as a library and a binary. See spec/abaigrammar.md. */\n\nexport { type AbaikitHandlers, createAbaikit } from \"./abaigram.ts\";\nexport { main, type ParsedArgs, parseArgs, UsageError } from \"./bin.ts\";\nexport { ABAIKIT_VERSION } from \"./version.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** abaikit: the abai authoring tool, as a library and a binary. See spec/abaigrammar.md. */
|
|
2
|
+
export { createAbaikit } from "./abaigram.js";
|
|
3
|
+
export { main, parseArgs, UsageError } from "./bin.js";
|
|
4
|
+
export { ABAIKIT_VERSION } from "./version.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAE5F,OAAO,EAAwB,aAAa,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,IAAI,EAAmB,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC","sourcesContent":["/** abaikit: the abai authoring tool, as a library and a binary. See spec/abaigrammar.md. */\n\nexport { type AbaikitHandlers, createAbaikit } from \"./abaigram.ts\";\nexport { main, type ParsedArgs, parseArgs, UsageError } from \"./bin.ts\";\nexport { ABAIKIT_VERSION } from \"./version.ts\";\n"]}
|
package/dist/io.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Where the binary writes; the stub's entry io, so `abaikit abai ...` shares it. Injectable for tests. */
|
|
2
|
+
export interface AbaikitIo {
|
|
3
|
+
out(text: string): void;
|
|
4
|
+
err(text: string): void;
|
|
5
|
+
env: NodeJS.ProcessEnv;
|
|
6
|
+
cwd: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const processIo: AbaikitIo;
|
|
9
|
+
export declare class UsageError extends Error {
|
|
10
|
+
}
|
|
11
|
+
export declare function errorMessage(error: unknown): string;
|
|
12
|
+
//# sourceMappingURL=io.d.ts.map
|
package/dist/io.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAGA,2GAA2G;AAC3G,MAAM,WAAW,SAAS;IACzB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,eAAO,MAAM,SAAS,EAAE,SAWvB,CAAC;AAEF,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD","sourcesContent":["// What every verb shares: where the binary writes, and the error that\n// means \"the arguments were wrong\" (exit 2, the usage follows).\n\n/** Where the binary writes; the stub's entry io, so `abaikit abai ...` shares it. Injectable for tests. */\nexport interface AbaikitIo {\n\tout(text: string): void;\n\terr(text: string): void;\n\tenv: NodeJS.ProcessEnv;\n\tcwd: string;\n}\n\nexport const processIo: AbaikitIo = {\n\tout: (text) => {\n\t\tprocess.stdout.write(text);\n\t},\n\terr: (text) => {\n\t\tprocess.stderr.write(`${text}\\n`);\n\t},\n\tenv: process.env,\n\tget cwd() {\n\t\treturn process.cwd();\n\t},\n};\n\nexport class UsageError extends Error {}\n\nexport function errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n"]}
|
package/dist/io.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// What every verb shares: where the binary writes, and the error that
|
|
2
|
+
// means "the arguments were wrong" (exit 2, the usage follows).
|
|
3
|
+
export const processIo = {
|
|
4
|
+
out: (text) => {
|
|
5
|
+
process.stdout.write(text);
|
|
6
|
+
},
|
|
7
|
+
err: (text) => {
|
|
8
|
+
process.stderr.write(`${text}\n`);
|
|
9
|
+
},
|
|
10
|
+
env: process.env,
|
|
11
|
+
get cwd() {
|
|
12
|
+
return process.cwd();
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export class UsageError extends Error {
|
|
16
|
+
}
|
|
17
|
+
export function errorMessage(error) {
|
|
18
|
+
return error instanceof Error ? error.message : String(error);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=io.js.map
|
package/dist/io.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,gEAAgE;AAUhE,MAAM,CAAC,MAAM,SAAS,GAAc;IACnC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAAA,CAC3B;IACD,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IAAA,CAClC;IACD,GAAG,EAAE,OAAO,CAAC,GAAG;IAChB,IAAI,GAAG,GAAG;QACT,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC;IAAA,CACrB;CACD,CAAC;AAEF,MAAM,OAAO,UAAW,SAAQ,KAAK;CAAG;AAExC,MAAM,UAAU,YAAY,CAAC,KAAc,EAAU;IACpD,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAAA,CAC9D","sourcesContent":["// What every verb shares: where the binary writes, and the error that\n// means \"the arguments were wrong\" (exit 2, the usage follows).\n\n/** Where the binary writes; the stub's entry io, so `abaikit abai ...` shares it. Injectable for tests. */\nexport interface AbaikitIo {\n\tout(text: string): void;\n\terr(text: string): void;\n\tenv: NodeJS.ProcessEnv;\n\tcwd: string;\n}\n\nexport const processIo: AbaikitIo = {\n\tout: (text) => {\n\t\tprocess.stdout.write(text);\n\t},\n\terr: (text) => {\n\t\tprocess.stderr.write(`${text}\\n`);\n\t},\n\tenv: process.env,\n\tget cwd() {\n\t\treturn process.cwd();\n\t},\n};\n\nexport class UsageError extends Error {}\n\nexport function errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ParsedArgs } from "./bin.ts";
|
|
2
|
+
import { type AbaikitIo } from "./io.ts";
|
|
3
|
+
export declare const GUIDE_FILE: string;
|
|
4
|
+
export declare const SKILL_NAME = "learnabai";
|
|
5
|
+
export interface GuideSection {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PackageSpecs {
|
|
11
|
+
/** `npm install` arguments: `file:` paths, or `name@^version`. */
|
|
12
|
+
abai: string;
|
|
13
|
+
harness: string;
|
|
14
|
+
/** The same packages as package.json values: `file:` paths, or `^version`; abaikit itself for a devDependency. */
|
|
15
|
+
dependency: {
|
|
16
|
+
abai: string;
|
|
17
|
+
harness: string;
|
|
18
|
+
abaikit: string;
|
|
19
|
+
};
|
|
20
|
+
/** The specs are `file:` paths into a workspace checkout, not published versions. */
|
|
21
|
+
fromWorkspace: boolean;
|
|
22
|
+
/** Workspace checkouts only: both packages have their `dist/` built, so `npm install` of them works now. */
|
|
23
|
+
built: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface Guide {
|
|
26
|
+
abaigrammar: string;
|
|
27
|
+
stub: string;
|
|
28
|
+
abaikit: string;
|
|
29
|
+
/** How this machine installs the stub and the harness. */
|
|
30
|
+
packages: PackageSpecs;
|
|
31
|
+
header: string;
|
|
32
|
+
sections: GuideSection[];
|
|
33
|
+
text: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The specs `npm install` takes for the two packages an abaigram needs, as
|
|
37
|
+
* this abaikit can reach them. Inside the workspace checkout (the parent
|
|
38
|
+
* is the workspace root whose `workspaces` name abaikit, not some
|
|
39
|
+
* `node_modules` where hoisted siblings happen to sit) they are `file:`
|
|
40
|
+
* paths to the sibling packages; anywhere else, the published versions.
|
|
41
|
+
*/
|
|
42
|
+
export declare function packageSpecs(packageRoot?: string): PackageSpecs;
|
|
43
|
+
/** The guide, filled in and split into its sections. */
|
|
44
|
+
export declare function loadGuide(file?: string, packageRoot?: string): Guide;
|
|
45
|
+
/** One section by id; an unknown id names the ids there are. */
|
|
46
|
+
export declare function findSection(guide: Guide, id: string): GuideSection;
|
|
47
|
+
/** The whole guide, or one section, as printed. */
|
|
48
|
+
export declare function guideText(section?: string): string;
|
|
49
|
+
/** The `learnabai` capability: the guide for an agent, one section or all. */
|
|
50
|
+
export declare const learnabaiHandler: (params: Record<string, unknown>) => {
|
|
51
|
+
text: string;
|
|
52
|
+
};
|
|
53
|
+
/** The skill file Claude Code loads as `/learnabai`: the whole guide, with its source named. */
|
|
54
|
+
export declare function skillText(guide: Guide): string;
|
|
55
|
+
/** Where `--skill` writes: `<dir>/learnabai/SKILL.md`, the dir defaulting to Claude Code's user skills. */
|
|
56
|
+
export declare function skillPath(io: AbaikitIo, dir?: string): string;
|
|
57
|
+
export declare function runLearnabai(parsed: Extract<ParsedArgs, {
|
|
58
|
+
verb: "learnabai";
|
|
59
|
+
}>, io: AbaikitIo): Promise<number>;
|
|
60
|
+
//# sourceMappingURL=learnabai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnabai.d.ts","sourceRoot":"","sources":["../src/learnabai.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,SAAS,CAAC;AAGrD,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,eAAO,MAAM,UAAU,cAAc,CAAC;AAEtC,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC5B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,kHAAkH;IAClH,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,qFAAqF;IACrF,aAAa,EAAE,OAAO,CAAC;IACvB,4GAA4G;IAC5G,KAAK,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,KAAK;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACb;AAcD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,WAAW,GAAE,MAAqB,GAAG,YAAY,CA6B7E;AAOD,wDAAwD;AACxD,wBAAgB,SAAS,CAAC,IAAI,GAAE,MAAmB,EAAE,WAAW,GAAE,MAAqB,GAAG,KAAK,CAsD9F;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,GAAG,YAAY,CAIlE;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAIlD;AAED,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB;;CAG5B,CAAC;AAEF,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAY9C;AAED,2GAA2G;AAC3G,wBAAgB,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBrH","sourcesContent":["// abaikit learnabai: the guide an agent reads before it creates or\n// retrofits an abaigram. One text, `abai/learnabai.md`, delivered three\n// ways: printed by the verb (whole, one section, or as JSON), returned by\n// the `learnabai` capability to an agent on the MCP front end, and written\n// as a Claude Code skill by `--skill`. The text names this machine's way\n// to install the packages and the stub's own deviations, filled in here,\n// so it is never out of step with what is installed.\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { basename, join, resolve } from \"node:path\";\nimport {\n\tABAI_STUB_VERSION,\n\tABAIGRAMMAR_VERSION,\n\tINSTANTIATION_DEVIATIONS,\n\tSIDECAR_DEVIATION,\n\tTHREAD_DEVIATION,\n} from \"@abaigram/abai-stub\";\nimport { PACKAGE_ROOT } from \"./abaigram.ts\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport { type AbaikitIo, UsageError } from \"./io.ts\";\nimport { ABAIKIT_VERSION } from \"./version.ts\";\n\nexport const GUIDE_FILE = join(PACKAGE_ROOT, \"abai\", \"learnabai.md\");\nexport const SKILL_NAME = \"learnabai\";\n\nexport interface GuideSection {\n\tid: string;\n\ttitle: string;\n\ttext: string;\n}\n\nexport interface PackageSpecs {\n\t/** `npm install` arguments: `file:` paths, or `name@^version`. */\n\tabai: string;\n\tharness: string;\n\t/** The same packages as package.json values: `file:` paths, or `^version`; abaikit itself for a devDependency. */\n\tdependency: { abai: string; harness: string; abaikit: string };\n\t/** The specs are `file:` paths into a workspace checkout, not published versions. */\n\tfromWorkspace: boolean;\n\t/** Workspace checkouts only: both packages have their `dist/` built, so `npm install` of them works now. */\n\tbuilt: boolean;\n}\n\nexport interface Guide {\n\tabaigrammar: string;\n\tstub: string;\n\tabaikit: string;\n\t/** How this machine installs the stub and the harness. */\n\tpackages: PackageSpecs;\n\theader: string;\n\tsections: GuideSection[];\n\ttext: string;\n}\n\nconst require = createRequire(import.meta.url);\n\nfunction readPackage(dir: string): { name?: unknown; workspaces?: unknown } | undefined {\n\tconst path = join(dir, \"package.json\");\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\treturn JSON.parse(readFileSync(path, \"utf8\")) as { name?: unknown; workspaces?: unknown };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The specs `npm install` takes for the two packages an abaigram needs, as\n * this abaikit can reach them. Inside the workspace checkout (the parent\n * is the workspace root whose `workspaces` name abaikit, not some\n * `node_modules` where hoisted siblings happen to sit) they are `file:`\n * paths to the sibling packages; anywhere else, the published versions.\n */\nexport function packageSpecs(packageRoot: string = PACKAGE_ROOT): PackageSpecs {\n\tconst workspace = resolve(packageRoot, \"..\");\n\tconst root = readPackage(workspace);\n\tconst workspaces = Array.isArray(root?.workspaces) ? (root.workspaces as unknown[]) : [];\n\tconst inWorkspace = basename(workspace) !== \"node_modules\" && workspaces.includes(basename(packageRoot));\n\tconst abai = join(workspace, \"abai-stub\");\n\tconst harness = join(workspace, \"abai-embedded-harness\");\n\tif (\n\t\tinWorkspace &&\n\t\treadPackage(abai)?.name === \"@abaigram/abai-stub\" &&\n\t\treadPackage(harness)?.name === \"@abaigram/abai-embedded-harness\"\n\t) {\n\t\tconst built = existsSync(join(abai, \"dist\", \"index.js\")) && existsSync(join(harness, \"dist\", \"index.js\"));\n\t\treturn {\n\t\t\tabai: `file:${abai}`,\n\t\t\tharness: `file:${harness}`,\n\t\t\tdependency: { abai: `file:${abai}`, harness: `file:${harness}`, abaikit: `file:${resolve(packageRoot)}` },\n\t\t\tfromWorkspace: true,\n\t\t\tbuilt,\n\t\t};\n\t}\n\tconst harnessVersion = (require(\"@abaigram/abai-embedded-harness/package.json\") as { version: string }).version;\n\treturn {\n\t\tabai: `@abaigram/abai-stub@^${ABAI_STUB_VERSION}`,\n\t\tharness: `@abaigram/abai-embedded-harness@^${harnessVersion}`,\n\t\tdependency: { abai: `^${ABAI_STUB_VERSION}`, harness: `^${harnessVersion}`, abaikit: `^${ABAIKIT_VERSION}` },\n\t\tfromWorkspace: false,\n\t\tbuilt: true,\n\t};\n}\n\n/** The text with every fenced block blanked, so a heading inside one is not a section. */\nfunction outsideFences(text: string): string {\n\treturn text.replace(/^```[a-z]*\\n[\\s\\S]*?^```$/gm, (block) => block.replace(/[^\\n]/g, \" \"));\n}\n\n/** The guide, filled in and split into its sections. */\nexport function loadGuide(file: string = GUIDE_FILE, packageRoot: string = PACKAGE_ROOT): Guide {\n\tconst packages = packageSpecs(packageRoot);\n\tconst deviations = [...INSTANTIATION_DEVIATIONS, THREAD_DEVIATION, SIDECAR_DEVIATION]\n\t\t.map((d) => `- ${d}`)\n\t\t.join(\"\\n\");\n\tlet workspaceNote = \"\";\n\tif (packages.fromWorkspace && !packages.built) {\n\t\tworkspaceNote =\n\t\t\t\"\\nThese are workspace checkouts whose `dist/` is not built yet: run `npm run build` in the workspace first, or the packages install with nothing to import.\\n\";\n\t} else if (packages.fromWorkspace) {\n\t\tworkspaceNote = \"\\n(These are the workspace's own checkouts, built; `npm install` links them.)\\n\";\n\t}\n\tconst values: Record<string, string> = {\n\t\tabaigrammar: ABAIGRAMMAR_VERSION,\n\t\tstub: ABAI_STUB_VERSION,\n\t\tabaikit: ABAIKIT_VERSION,\n\t\tabai_spec: packages.abai,\n\t\tharness_spec: packages.harness,\n\t\tworkspace_note: workspaceNote,\n\t\tdeviations,\n\t};\n\tconst text = readFileSync(file, \"utf8\").replace(/\\{\\{([a-z_]+)\\}\\}/g, (whole, key: string) => {\n\t\tconst value = values[key];\n\t\tif (value === undefined) throw new Error(`${file} names an unknown value ${whole}`);\n\t\treturn value;\n\t});\n\t// sections are the level-two headings outside fenced blocks, each ending in its id in parentheses\n\tconst scan = outsideFences(text);\n\tconst sections: GuideSection[] = [];\n\tlet header = text;\n\tlet previous: { id: string; title: string; start: number } | undefined;\n\tfor (const match of scan.matchAll(/^## (.*)$/gm)) {\n\t\tconst titled = /^(.+?) \\(([a-z]+)\\)$/.exec(match[1]);\n\t\tif (!titled) throw new Error(`${file}: the heading \"## ${match[1]}\" ends in no (id)`);\n\t\tconst [, title, id] = titled;\n\t\tif (sections.some((s) => s.id === id) || previous?.id === id) {\n\t\t\tthrow new Error(`${file}: two sections are named (${id})`);\n\t\t}\n\t\tconst start = match.index;\n\t\tif (previous) {\n\t\t\tsections.push({ id: previous.id, title: previous.title, text: text.slice(previous.start, start).trimEnd() });\n\t\t} else header = text.slice(0, start).trimEnd();\n\t\tprevious = { id, title, start };\n\t}\n\tif (previous) sections.push({ id: previous.id, title: previous.title, text: text.slice(previous.start).trimEnd() });\n\treturn {\n\t\tabaigrammar: ABAIGRAMMAR_VERSION,\n\t\tstub: ABAI_STUB_VERSION,\n\t\tabaikit: ABAIKIT_VERSION,\n\t\tpackages,\n\t\theader,\n\t\tsections,\n\t\ttext,\n\t};\n}\n\n/** One section by id; an unknown id names the ids there are. */\nexport function findSection(guide: Guide, id: string): GuideSection {\n\tconst found = guide.sections.find((s) => s.id === id);\n\tif (!found) throw new UsageError(`no section ${id}; the sections are ${guide.sections.map((s) => s.id).join(\", \")}`);\n\treturn found;\n}\n\n/** The whole guide, or one section, as printed. */\nexport function guideText(section?: string): string {\n\tconst guide = loadGuide();\n\tif (section === undefined || section === \"\") return guide.text;\n\treturn `${findSection(guide, section).text}\\n`;\n}\n\n/** The `learnabai` capability: the guide for an agent, one section or all. */\nexport const learnabaiHandler = (params: Record<string, unknown>): { text: string } => {\n\tconst section = typeof params.section === \"string\" ? params.section : undefined;\n\treturn { text: guideText(section) };\n};\n\n/** The skill file Claude Code loads as `/learnabai`: the whole guide, with its source named. */\nexport function skillText(guide: Guide): string {\n\treturn [\n\t\t\"---\",\n\t\t`name: ${SKILL_NAME}`,\n\t\t\"description: How to create an abaigram or retrofit a repository into one with abai (the stub, the standard entry, the conformance levels, the step-by-step retrofit, and what never to do). Use when asked to abaify a repository, add abai to a program, or check an abaigram's conformance.\",\n\t\t\"---\",\n\t\t\"\",\n\t\t`This is the output of \\`abaikit learnabai\\` for abaikit ${guide.abaikit}; run that command for the current text and \\`abaikit learnabai --section <id>\\` for one section.`,\n\t\t\"\",\n\t\tguide.text.trimEnd(),\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\n/** Where `--skill` writes: `<dir>/learnabai/SKILL.md`, the dir defaulting to Claude Code's user skills. */\nexport function skillPath(io: AbaikitIo, dir?: string): string {\n\tif (dir !== undefined) return join(resolve(io.cwd, dir), SKILL_NAME, \"SKILL.md\");\n\tconst home = io.env.HOME;\n\tif (!home) throw new UsageError(\"--skill needs HOME, or --skill-dir <dir>, to know where Claude Code's skills live\");\n\treturn join(home, \".claude\", \"skills\", SKILL_NAME, \"SKILL.md\");\n}\n\nexport async function runLearnabai(parsed: Extract<ParsedArgs, { verb: \"learnabai\" }>, io: AbaikitIo): Promise<number> {\n\tif (parsed.skill) {\n\t\tconst path = skillPath(io, parsed.skillDir);\n\t\tmkdirSync(join(path, \"..\"), { recursive: true });\n\t\twriteFileSync(path, skillText(loadGuide()));\n\t\tio.out(`${path}\\n`);\n\t\treturn 0;\n\t}\n\tif (parsed.json) {\n\t\tconst guide = loadGuide();\n\t\tconst sections: Record<string, string> = {};\n\t\tconst picked = parsed.section === undefined ? guide.sections : [findSection(guide, parsed.section)];\n\t\tfor (const s of picked) sections[s.id] = s.text;\n\t\tconst json = {\n\t\t\tabaigrammar: guide.abaigrammar,\n\t\t\tstub: guide.stub,\n\t\t\tabaikit: guide.abaikit,\n\t\t\tpackages: guide.packages,\n\t\t\tsections,\n\t\t};\n\t\tio.out(`${JSON.stringify(json, null, 2)}\\n`);\n\t\treturn 0;\n\t}\n\tio.out(guideText(parsed.section));\n\treturn 0;\n}\n"]}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// abaikit learnabai: the guide an agent reads before it creates or
|
|
2
|
+
// retrofits an abaigram. One text, `abai/learnabai.md`, delivered three
|
|
3
|
+
// ways: printed by the verb (whole, one section, or as JSON), returned by
|
|
4
|
+
// the `learnabai` capability to an agent on the MCP front end, and written
|
|
5
|
+
// as a Claude Code skill by `--skill`. The text names this machine's way
|
|
6
|
+
// to install the packages and the stub's own deviations, filled in here,
|
|
7
|
+
// so it is never out of step with what is installed.
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { basename, join, resolve } from "node:path";
|
|
11
|
+
import { ABAI_STUB_VERSION, ABAIGRAMMAR_VERSION, INSTANTIATION_DEVIATIONS, SIDECAR_DEVIATION, THREAD_DEVIATION, } from "@abaigram/abai-stub";
|
|
12
|
+
import { PACKAGE_ROOT } from "./abaigram.js";
|
|
13
|
+
import { UsageError } from "./io.js";
|
|
14
|
+
import { ABAIKIT_VERSION } from "./version.js";
|
|
15
|
+
export const GUIDE_FILE = join(PACKAGE_ROOT, "abai", "learnabai.md");
|
|
16
|
+
export const SKILL_NAME = "learnabai";
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
function readPackage(dir) {
|
|
19
|
+
const path = join(dir, "package.json");
|
|
20
|
+
if (!existsSync(path))
|
|
21
|
+
return undefined;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The specs `npm install` takes for the two packages an abaigram needs, as
|
|
31
|
+
* this abaikit can reach them. Inside the workspace checkout (the parent
|
|
32
|
+
* is the workspace root whose `workspaces` name abaikit, not some
|
|
33
|
+
* `node_modules` where hoisted siblings happen to sit) they are `file:`
|
|
34
|
+
* paths to the sibling packages; anywhere else, the published versions.
|
|
35
|
+
*/
|
|
36
|
+
export function packageSpecs(packageRoot = PACKAGE_ROOT) {
|
|
37
|
+
const workspace = resolve(packageRoot, "..");
|
|
38
|
+
const root = readPackage(workspace);
|
|
39
|
+
const workspaces = Array.isArray(root?.workspaces) ? root.workspaces : [];
|
|
40
|
+
const inWorkspace = basename(workspace) !== "node_modules" && workspaces.includes(basename(packageRoot));
|
|
41
|
+
const abai = join(workspace, "abai-stub");
|
|
42
|
+
const harness = join(workspace, "abai-embedded-harness");
|
|
43
|
+
if (inWorkspace &&
|
|
44
|
+
readPackage(abai)?.name === "@abaigram/abai-stub" &&
|
|
45
|
+
readPackage(harness)?.name === "@abaigram/abai-embedded-harness") {
|
|
46
|
+
const built = existsSync(join(abai, "dist", "index.js")) && existsSync(join(harness, "dist", "index.js"));
|
|
47
|
+
return {
|
|
48
|
+
abai: `file:${abai}`,
|
|
49
|
+
harness: `file:${harness}`,
|
|
50
|
+
dependency: { abai: `file:${abai}`, harness: `file:${harness}`, abaikit: `file:${resolve(packageRoot)}` },
|
|
51
|
+
fromWorkspace: true,
|
|
52
|
+
built,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const harnessVersion = require("@abaigram/abai-embedded-harness/package.json").version;
|
|
56
|
+
return {
|
|
57
|
+
abai: `@abaigram/abai-stub@^${ABAI_STUB_VERSION}`,
|
|
58
|
+
harness: `@abaigram/abai-embedded-harness@^${harnessVersion}`,
|
|
59
|
+
dependency: { abai: `^${ABAI_STUB_VERSION}`, harness: `^${harnessVersion}`, abaikit: `^${ABAIKIT_VERSION}` },
|
|
60
|
+
fromWorkspace: false,
|
|
61
|
+
built: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** The text with every fenced block blanked, so a heading inside one is not a section. */
|
|
65
|
+
function outsideFences(text) {
|
|
66
|
+
return text.replace(/^```[a-z]*\n[\s\S]*?^```$/gm, (block) => block.replace(/[^\n]/g, " "));
|
|
67
|
+
}
|
|
68
|
+
/** The guide, filled in and split into its sections. */
|
|
69
|
+
export function loadGuide(file = GUIDE_FILE, packageRoot = PACKAGE_ROOT) {
|
|
70
|
+
const packages = packageSpecs(packageRoot);
|
|
71
|
+
const deviations = [...INSTANTIATION_DEVIATIONS, THREAD_DEVIATION, SIDECAR_DEVIATION]
|
|
72
|
+
.map((d) => `- ${d}`)
|
|
73
|
+
.join("\n");
|
|
74
|
+
let workspaceNote = "";
|
|
75
|
+
if (packages.fromWorkspace && !packages.built) {
|
|
76
|
+
workspaceNote =
|
|
77
|
+
"\nThese are workspace checkouts whose `dist/` is not built yet: run `npm run build` in the workspace first, or the packages install with nothing to import.\n";
|
|
78
|
+
}
|
|
79
|
+
else if (packages.fromWorkspace) {
|
|
80
|
+
workspaceNote = "\n(These are the workspace's own checkouts, built; `npm install` links them.)\n";
|
|
81
|
+
}
|
|
82
|
+
const values = {
|
|
83
|
+
abaigrammar: ABAIGRAMMAR_VERSION,
|
|
84
|
+
stub: ABAI_STUB_VERSION,
|
|
85
|
+
abaikit: ABAIKIT_VERSION,
|
|
86
|
+
abai_spec: packages.abai,
|
|
87
|
+
harness_spec: packages.harness,
|
|
88
|
+
workspace_note: workspaceNote,
|
|
89
|
+
deviations,
|
|
90
|
+
};
|
|
91
|
+
const text = readFileSync(file, "utf8").replace(/\{\{([a-z_]+)\}\}/g, (whole, key) => {
|
|
92
|
+
const value = values[key];
|
|
93
|
+
if (value === undefined)
|
|
94
|
+
throw new Error(`${file} names an unknown value ${whole}`);
|
|
95
|
+
return value;
|
|
96
|
+
});
|
|
97
|
+
// sections are the level-two headings outside fenced blocks, each ending in its id in parentheses
|
|
98
|
+
const scan = outsideFences(text);
|
|
99
|
+
const sections = [];
|
|
100
|
+
let header = text;
|
|
101
|
+
let previous;
|
|
102
|
+
for (const match of scan.matchAll(/^## (.*)$/gm)) {
|
|
103
|
+
const titled = /^(.+?) \(([a-z]+)\)$/.exec(match[1]);
|
|
104
|
+
if (!titled)
|
|
105
|
+
throw new Error(`${file}: the heading "## ${match[1]}" ends in no (id)`);
|
|
106
|
+
const [, title, id] = titled;
|
|
107
|
+
if (sections.some((s) => s.id === id) || previous?.id === id) {
|
|
108
|
+
throw new Error(`${file}: two sections are named (${id})`);
|
|
109
|
+
}
|
|
110
|
+
const start = match.index;
|
|
111
|
+
if (previous) {
|
|
112
|
+
sections.push({ id: previous.id, title: previous.title, text: text.slice(previous.start, start).trimEnd() });
|
|
113
|
+
}
|
|
114
|
+
else
|
|
115
|
+
header = text.slice(0, start).trimEnd();
|
|
116
|
+
previous = { id, title, start };
|
|
117
|
+
}
|
|
118
|
+
if (previous)
|
|
119
|
+
sections.push({ id: previous.id, title: previous.title, text: text.slice(previous.start).trimEnd() });
|
|
120
|
+
return {
|
|
121
|
+
abaigrammar: ABAIGRAMMAR_VERSION,
|
|
122
|
+
stub: ABAI_STUB_VERSION,
|
|
123
|
+
abaikit: ABAIKIT_VERSION,
|
|
124
|
+
packages,
|
|
125
|
+
header,
|
|
126
|
+
sections,
|
|
127
|
+
text,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** One section by id; an unknown id names the ids there are. */
|
|
131
|
+
export function findSection(guide, id) {
|
|
132
|
+
const found = guide.sections.find((s) => s.id === id);
|
|
133
|
+
if (!found)
|
|
134
|
+
throw new UsageError(`no section ${id}; the sections are ${guide.sections.map((s) => s.id).join(", ")}`);
|
|
135
|
+
return found;
|
|
136
|
+
}
|
|
137
|
+
/** The whole guide, or one section, as printed. */
|
|
138
|
+
export function guideText(section) {
|
|
139
|
+
const guide = loadGuide();
|
|
140
|
+
if (section === undefined || section === "")
|
|
141
|
+
return guide.text;
|
|
142
|
+
return `${findSection(guide, section).text}\n`;
|
|
143
|
+
}
|
|
144
|
+
/** The `learnabai` capability: the guide for an agent, one section or all. */
|
|
145
|
+
export const learnabaiHandler = (params) => {
|
|
146
|
+
const section = typeof params.section === "string" ? params.section : undefined;
|
|
147
|
+
return { text: guideText(section) };
|
|
148
|
+
};
|
|
149
|
+
/** The skill file Claude Code loads as `/learnabai`: the whole guide, with its source named. */
|
|
150
|
+
export function skillText(guide) {
|
|
151
|
+
return [
|
|
152
|
+
"---",
|
|
153
|
+
`name: ${SKILL_NAME}`,
|
|
154
|
+
"description: How to create an abaigram or retrofit a repository into one with abai (the stub, the standard entry, the conformance levels, the step-by-step retrofit, and what never to do). Use when asked to abaify a repository, add abai to a program, or check an abaigram's conformance.",
|
|
155
|
+
"---",
|
|
156
|
+
"",
|
|
157
|
+
`This is the output of \`abaikit learnabai\` for abaikit ${guide.abaikit}; run that command for the current text and \`abaikit learnabai --section <id>\` for one section.`,
|
|
158
|
+
"",
|
|
159
|
+
guide.text.trimEnd(),
|
|
160
|
+
"",
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
/** Where `--skill` writes: `<dir>/learnabai/SKILL.md`, the dir defaulting to Claude Code's user skills. */
|
|
164
|
+
export function skillPath(io, dir) {
|
|
165
|
+
if (dir !== undefined)
|
|
166
|
+
return join(resolve(io.cwd, dir), SKILL_NAME, "SKILL.md");
|
|
167
|
+
const home = io.env.HOME;
|
|
168
|
+
if (!home)
|
|
169
|
+
throw new UsageError("--skill needs HOME, or --skill-dir <dir>, to know where Claude Code's skills live");
|
|
170
|
+
return join(home, ".claude", "skills", SKILL_NAME, "SKILL.md");
|
|
171
|
+
}
|
|
172
|
+
export async function runLearnabai(parsed, io) {
|
|
173
|
+
if (parsed.skill) {
|
|
174
|
+
const path = skillPath(io, parsed.skillDir);
|
|
175
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
176
|
+
writeFileSync(path, skillText(loadGuide()));
|
|
177
|
+
io.out(`${path}\n`);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
if (parsed.json) {
|
|
181
|
+
const guide = loadGuide();
|
|
182
|
+
const sections = {};
|
|
183
|
+
const picked = parsed.section === undefined ? guide.sections : [findSection(guide, parsed.section)];
|
|
184
|
+
for (const s of picked)
|
|
185
|
+
sections[s.id] = s.text;
|
|
186
|
+
const json = {
|
|
187
|
+
abaigrammar: guide.abaigrammar,
|
|
188
|
+
stub: guide.stub,
|
|
189
|
+
abaikit: guide.abaikit,
|
|
190
|
+
packages: guide.packages,
|
|
191
|
+
sections,
|
|
192
|
+
};
|
|
193
|
+
io.out(`${JSON.stringify(json, null, 2)}\n`);
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
io.out(guideText(parsed.section));
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=learnabai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnabai.js","sourceRoot":"","sources":["../src/learnabai.ts"],"names":[],"mappings":"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","sourcesContent":["// abaikit learnabai: the guide an agent reads before it creates or\n// retrofits an abaigram. One text, `abai/learnabai.md`, delivered three\n// ways: printed by the verb (whole, one section, or as JSON), returned by\n// the `learnabai` capability to an agent on the MCP front end, and written\n// as a Claude Code skill by `--skill`. The text names this machine's way\n// to install the packages and the stub's own deviations, filled in here,\n// so it is never out of step with what is installed.\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { basename, join, resolve } from \"node:path\";\nimport {\n\tABAI_STUB_VERSION,\n\tABAIGRAMMAR_VERSION,\n\tINSTANTIATION_DEVIATIONS,\n\tSIDECAR_DEVIATION,\n\tTHREAD_DEVIATION,\n} from \"@abaigram/abai-stub\";\nimport { PACKAGE_ROOT } from \"./abaigram.ts\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport { type AbaikitIo, UsageError } from \"./io.ts\";\nimport { ABAIKIT_VERSION } from \"./version.ts\";\n\nexport const GUIDE_FILE = join(PACKAGE_ROOT, \"abai\", \"learnabai.md\");\nexport const SKILL_NAME = \"learnabai\";\n\nexport interface GuideSection {\n\tid: string;\n\ttitle: string;\n\ttext: string;\n}\n\nexport interface PackageSpecs {\n\t/** `npm install` arguments: `file:` paths, or `name@^version`. */\n\tabai: string;\n\tharness: string;\n\t/** The same packages as package.json values: `file:` paths, or `^version`; abaikit itself for a devDependency. */\n\tdependency: { abai: string; harness: string; abaikit: string };\n\t/** The specs are `file:` paths into a workspace checkout, not published versions. */\n\tfromWorkspace: boolean;\n\t/** Workspace checkouts only: both packages have their `dist/` built, so `npm install` of them works now. */\n\tbuilt: boolean;\n}\n\nexport interface Guide {\n\tabaigrammar: string;\n\tstub: string;\n\tabaikit: string;\n\t/** How this machine installs the stub and the harness. */\n\tpackages: PackageSpecs;\n\theader: string;\n\tsections: GuideSection[];\n\ttext: string;\n}\n\nconst require = createRequire(import.meta.url);\n\nfunction readPackage(dir: string): { name?: unknown; workspaces?: unknown } | undefined {\n\tconst path = join(dir, \"package.json\");\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\treturn JSON.parse(readFileSync(path, \"utf8\")) as { name?: unknown; workspaces?: unknown };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The specs `npm install` takes for the two packages an abaigram needs, as\n * this abaikit can reach them. Inside the workspace checkout (the parent\n * is the workspace root whose `workspaces` name abaikit, not some\n * `node_modules` where hoisted siblings happen to sit) they are `file:`\n * paths to the sibling packages; anywhere else, the published versions.\n */\nexport function packageSpecs(packageRoot: string = PACKAGE_ROOT): PackageSpecs {\n\tconst workspace = resolve(packageRoot, \"..\");\n\tconst root = readPackage(workspace);\n\tconst workspaces = Array.isArray(root?.workspaces) ? (root.workspaces as unknown[]) : [];\n\tconst inWorkspace = basename(workspace) !== \"node_modules\" && workspaces.includes(basename(packageRoot));\n\tconst abai = join(workspace, \"abai-stub\");\n\tconst harness = join(workspace, \"abai-embedded-harness\");\n\tif (\n\t\tinWorkspace &&\n\t\treadPackage(abai)?.name === \"@abaigram/abai-stub\" &&\n\t\treadPackage(harness)?.name === \"@abaigram/abai-embedded-harness\"\n\t) {\n\t\tconst built = existsSync(join(abai, \"dist\", \"index.js\")) && existsSync(join(harness, \"dist\", \"index.js\"));\n\t\treturn {\n\t\t\tabai: `file:${abai}`,\n\t\t\tharness: `file:${harness}`,\n\t\t\tdependency: { abai: `file:${abai}`, harness: `file:${harness}`, abaikit: `file:${resolve(packageRoot)}` },\n\t\t\tfromWorkspace: true,\n\t\t\tbuilt,\n\t\t};\n\t}\n\tconst harnessVersion = (require(\"@abaigram/abai-embedded-harness/package.json\") as { version: string }).version;\n\treturn {\n\t\tabai: `@abaigram/abai-stub@^${ABAI_STUB_VERSION}`,\n\t\tharness: `@abaigram/abai-embedded-harness@^${harnessVersion}`,\n\t\tdependency: { abai: `^${ABAI_STUB_VERSION}`, harness: `^${harnessVersion}`, abaikit: `^${ABAIKIT_VERSION}` },\n\t\tfromWorkspace: false,\n\t\tbuilt: true,\n\t};\n}\n\n/** The text with every fenced block blanked, so a heading inside one is not a section. */\nfunction outsideFences(text: string): string {\n\treturn text.replace(/^```[a-z]*\\n[\\s\\S]*?^```$/gm, (block) => block.replace(/[^\\n]/g, \" \"));\n}\n\n/** The guide, filled in and split into its sections. */\nexport function loadGuide(file: string = GUIDE_FILE, packageRoot: string = PACKAGE_ROOT): Guide {\n\tconst packages = packageSpecs(packageRoot);\n\tconst deviations = [...INSTANTIATION_DEVIATIONS, THREAD_DEVIATION, SIDECAR_DEVIATION]\n\t\t.map((d) => `- ${d}`)\n\t\t.join(\"\\n\");\n\tlet workspaceNote = \"\";\n\tif (packages.fromWorkspace && !packages.built) {\n\t\tworkspaceNote =\n\t\t\t\"\\nThese are workspace checkouts whose `dist/` is not built yet: run `npm run build` in the workspace first, or the packages install with nothing to import.\\n\";\n\t} else if (packages.fromWorkspace) {\n\t\tworkspaceNote = \"\\n(These are the workspace's own checkouts, built; `npm install` links them.)\\n\";\n\t}\n\tconst values: Record<string, string> = {\n\t\tabaigrammar: ABAIGRAMMAR_VERSION,\n\t\tstub: ABAI_STUB_VERSION,\n\t\tabaikit: ABAIKIT_VERSION,\n\t\tabai_spec: packages.abai,\n\t\tharness_spec: packages.harness,\n\t\tworkspace_note: workspaceNote,\n\t\tdeviations,\n\t};\n\tconst text = readFileSync(file, \"utf8\").replace(/\\{\\{([a-z_]+)\\}\\}/g, (whole, key: string) => {\n\t\tconst value = values[key];\n\t\tif (value === undefined) throw new Error(`${file} names an unknown value ${whole}`);\n\t\treturn value;\n\t});\n\t// sections are the level-two headings outside fenced blocks, each ending in its id in parentheses\n\tconst scan = outsideFences(text);\n\tconst sections: GuideSection[] = [];\n\tlet header = text;\n\tlet previous: { id: string; title: string; start: number } | undefined;\n\tfor (const match of scan.matchAll(/^## (.*)$/gm)) {\n\t\tconst titled = /^(.+?) \\(([a-z]+)\\)$/.exec(match[1]);\n\t\tif (!titled) throw new Error(`${file}: the heading \"## ${match[1]}\" ends in no (id)`);\n\t\tconst [, title, id] = titled;\n\t\tif (sections.some((s) => s.id === id) || previous?.id === id) {\n\t\t\tthrow new Error(`${file}: two sections are named (${id})`);\n\t\t}\n\t\tconst start = match.index;\n\t\tif (previous) {\n\t\t\tsections.push({ id: previous.id, title: previous.title, text: text.slice(previous.start, start).trimEnd() });\n\t\t} else header = text.slice(0, start).trimEnd();\n\t\tprevious = { id, title, start };\n\t}\n\tif (previous) sections.push({ id: previous.id, title: previous.title, text: text.slice(previous.start).trimEnd() });\n\treturn {\n\t\tabaigrammar: ABAIGRAMMAR_VERSION,\n\t\tstub: ABAI_STUB_VERSION,\n\t\tabaikit: ABAIKIT_VERSION,\n\t\tpackages,\n\t\theader,\n\t\tsections,\n\t\ttext,\n\t};\n}\n\n/** One section by id; an unknown id names the ids there are. */\nexport function findSection(guide: Guide, id: string): GuideSection {\n\tconst found = guide.sections.find((s) => s.id === id);\n\tif (!found) throw new UsageError(`no section ${id}; the sections are ${guide.sections.map((s) => s.id).join(\", \")}`);\n\treturn found;\n}\n\n/** The whole guide, or one section, as printed. */\nexport function guideText(section?: string): string {\n\tconst guide = loadGuide();\n\tif (section === undefined || section === \"\") return guide.text;\n\treturn `${findSection(guide, section).text}\\n`;\n}\n\n/** The `learnabai` capability: the guide for an agent, one section or all. */\nexport const learnabaiHandler = (params: Record<string, unknown>): { text: string } => {\n\tconst section = typeof params.section === \"string\" ? params.section : undefined;\n\treturn { text: guideText(section) };\n};\n\n/** The skill file Claude Code loads as `/learnabai`: the whole guide, with its source named. */\nexport function skillText(guide: Guide): string {\n\treturn [\n\t\t\"---\",\n\t\t`name: ${SKILL_NAME}`,\n\t\t\"description: How to create an abaigram or retrofit a repository into one with abai (the stub, the standard entry, the conformance levels, the step-by-step retrofit, and what never to do). Use when asked to abaify a repository, add abai to a program, or check an abaigram's conformance.\",\n\t\t\"---\",\n\t\t\"\",\n\t\t`This is the output of \\`abaikit learnabai\\` for abaikit ${guide.abaikit}; run that command for the current text and \\`abaikit learnabai --section <id>\\` for one section.`,\n\t\t\"\",\n\t\tguide.text.trimEnd(),\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\n/** Where `--skill` writes: `<dir>/learnabai/SKILL.md`, the dir defaulting to Claude Code's user skills. */\nexport function skillPath(io: AbaikitIo, dir?: string): string {\n\tif (dir !== undefined) return join(resolve(io.cwd, dir), SKILL_NAME, \"SKILL.md\");\n\tconst home = io.env.HOME;\n\tif (!home) throw new UsageError(\"--skill needs HOME, or --skill-dir <dir>, to know where Claude Code's skills live\");\n\treturn join(home, \".claude\", \"skills\", SKILL_NAME, \"SKILL.md\");\n}\n\nexport async function runLearnabai(parsed: Extract<ParsedArgs, { verb: \"learnabai\" }>, io: AbaikitIo): Promise<number> {\n\tif (parsed.skill) {\n\t\tconst path = skillPath(io, parsed.skillDir);\n\t\tmkdirSync(join(path, \"..\"), { recursive: true });\n\t\twriteFileSync(path, skillText(loadGuide()));\n\t\tio.out(`${path}\\n`);\n\t\treturn 0;\n\t}\n\tif (parsed.json) {\n\t\tconst guide = loadGuide();\n\t\tconst sections: Record<string, string> = {};\n\t\tconst picked = parsed.section === undefined ? guide.sections : [findSection(guide, parsed.section)];\n\t\tfor (const s of picked) sections[s.id] = s.text;\n\t\tconst json = {\n\t\t\tabaigrammar: guide.abaigrammar,\n\t\t\tstub: guide.stub,\n\t\t\tabaikit: guide.abaikit,\n\t\t\tpackages: guide.packages,\n\t\t\tsections,\n\t\t};\n\t\tio.out(`${JSON.stringify(json, null, 2)}\\n`);\n\t\treturn 0;\n\t}\n\tio.out(guideText(parsed.section));\n\treturn 0;\n}\n"]}
|
package/dist/new.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ParsedArgs } from "./bin.ts";
|
|
2
|
+
import { type AbaikitIo } from "./io.ts";
|
|
3
|
+
export interface NewOptions {
|
|
4
|
+
name: string;
|
|
5
|
+
lang: string;
|
|
6
|
+
level?: number;
|
|
7
|
+
dir?: string;
|
|
8
|
+
abaiImport?: string;
|
|
9
|
+
cwd: string;
|
|
10
|
+
}
|
|
11
|
+
export interface NewResult {
|
|
12
|
+
path: string;
|
|
13
|
+
written: string[];
|
|
14
|
+
/** Something the person must do before the project runs, when there is one. */
|
|
15
|
+
note?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function createProject(options: NewOptions): NewResult;
|
|
18
|
+
/** The `new` capability: the same creation, for the agent on any front end. */
|
|
19
|
+
export declare function newHandler(io: Pick<AbaikitIo, "cwd">): (params: Record<string, unknown>) => {
|
|
20
|
+
path: string;
|
|
21
|
+
written: number;
|
|
22
|
+
};
|
|
23
|
+
export declare function runNew(parsed: Extract<ParsedArgs, {
|
|
24
|
+
verb: "new";
|
|
25
|
+
}>, io: AbaikitIo): Promise<number>;
|
|
26
|
+
//# sourceMappingURL=new.d.ts.map
|