@jbarket/loomai 0.5.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/CHANGELOG.md +475 -0
- package/LICENSE +661 -0
- package/README.md +702 -0
- package/assets/demo.cast +396 -0
- package/assets/skill/SKILL.md +192 -0
- package/dist/backends/embedding-runtime.d.ts +39 -0
- package/dist/backends/embedding-runtime.js +265 -0
- package/dist/backends/episodes.d.ts +36 -0
- package/dist/backends/episodes.js +126 -0
- package/dist/backends/fastembed.d.ts +18 -0
- package/dist/backends/fastembed.js +95 -0
- package/dist/backends/glob.d.ts +10 -0
- package/dist/backends/glob.js +16 -0
- package/dist/backends/index.d.ts +6 -0
- package/dist/backends/index.js +78 -0
- package/dist/backends/migrations.d.ts +38 -0
- package/dist/backends/migrations.js +157 -0
- package/dist/backends/mmr.d.ts +45 -0
- package/dist/backends/mmr.js +82 -0
- package/dist/backends/proposals.d.ts +65 -0
- package/dist/backends/proposals.js +182 -0
- package/dist/backends/recall-log.d.ts +42 -0
- package/dist/backends/recall-log.js +143 -0
- package/dist/backends/retry.d.ts +29 -0
- package/dist/backends/retry.js +60 -0
- package/dist/backends/salience.d.ts +57 -0
- package/dist/backends/salience.js +186 -0
- package/dist/backends/sqlite-knowledge.d.ts +37 -0
- package/dist/backends/sqlite-knowledge.js +788 -0
- package/dist/backends/sqlite-vec.d.ts +64 -0
- package/dist/backends/sqlite-vec.js +737 -0
- package/dist/backends/ttl.d.ts +24 -0
- package/dist/backends/ttl.js +50 -0
- package/dist/backends/types.d.ts +562 -0
- package/dist/backends/types.js +8 -0
- package/dist/blocks/harness.d.ts +45 -0
- package/dist/blocks/harness.js +142 -0
- package/dist/blocks/model.d.ts +4 -0
- package/dist/blocks/model.js +56 -0
- package/dist/blocks/types.d.ts +40 -0
- package/dist/blocks/types.js +34 -0
- package/dist/categories.d.ts +16 -0
- package/dist/categories.js +29 -0
- package/dist/cli/args.d.ts +21 -0
- package/dist/cli/args.js +47 -0
- package/dist/cli/bootstrap.d.ts +2 -0
- package/dist/cli/bootstrap.js +214 -0
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.js +206 -0
- package/dist/cli/forget.d.ts +2 -0
- package/dist/cli/forget.js +97 -0
- package/dist/cli/harness.d.ts +2 -0
- package/dist/cli/harness.js +91 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +127 -0
- package/dist/cli/inject.d.ts +2 -0
- package/dist/cli/inject.js +298 -0
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/install.js +137 -0
- package/dist/cli/io.d.ts +14 -0
- package/dist/cli/io.js +63 -0
- package/dist/cli/knowledge.d.ts +2 -0
- package/dist/cli/knowledge.js +216 -0
- package/dist/cli/memory.d.ts +2 -0
- package/dist/cli/memory.js +608 -0
- package/dist/cli/migrate.d.ts +2 -0
- package/dist/cli/migrate.js +116 -0
- package/dist/cli/recall.d.ts +2 -0
- package/dist/cli/recall.js +91 -0
- package/dist/cli/remember.d.ts +2 -0
- package/dist/cli/remember.js +111 -0
- package/dist/cli/serve.d.ts +2 -0
- package/dist/cli/serve.js +71 -0
- package/dist/cli/subcommands.d.ts +7 -0
- package/dist/cli/subcommands.js +11 -0
- package/dist/cli/tui/multi-select.d.ts +53 -0
- package/dist/cli/tui/multi-select.js +116 -0
- package/dist/cli/update-identity.d.ts +2 -0
- package/dist/cli/update-identity.js +90 -0
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.js +87 -0
- package/dist/cli/wake.d.ts +2 -0
- package/dist/cli/wake.js +53 -0
- package/dist/clients.d.ts +8 -0
- package/dist/clients.js +63 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.js +160 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +75 -0
- package/dist/injection/harnesses.d.ts +17 -0
- package/dist/injection/harnesses.js +51 -0
- package/dist/injection/render.d.ts +17 -0
- package/dist/injection/render.js +50 -0
- package/dist/injection/writer.d.ts +25 -0
- package/dist/injection/writer.js +118 -0
- package/dist/install/harnesses.d.ts +26 -0
- package/dist/install/harnesses.js +90 -0
- package/dist/install/names.d.ts +16 -0
- package/dist/install/names.js +36 -0
- package/dist/install/render.d.ts +18 -0
- package/dist/install/render.js +42 -0
- package/dist/install/skill-source.d.ts +1 -0
- package/dist/install/skill-source.js +11 -0
- package/dist/path-safety.d.ts +19 -0
- package/dist/path-safety.js +62 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +571 -0
- package/dist/tools/append-maintenance-log.d.ts +52 -0
- package/dist/tools/append-maintenance-log.js +103 -0
- package/dist/tools/archive.d.ts +2 -0
- package/dist/tools/archive.js +21 -0
- package/dist/tools/bootstrap.d.ts +11 -0
- package/dist/tools/bootstrap.js +161 -0
- package/dist/tools/dossier.d.ts +1 -0
- package/dist/tools/dossier.js +102 -0
- package/dist/tools/eurorack-import.d.ts +47 -0
- package/dist/tools/eurorack-import.js +199 -0
- package/dist/tools/find-similar.d.ts +3 -0
- package/dist/tools/find-similar.js +26 -0
- package/dist/tools/forget.d.ts +6 -0
- package/dist/tools/forget.js +70 -0
- package/dist/tools/harness.d.ts +18 -0
- package/dist/tools/harness.js +54 -0
- package/dist/tools/identity-scaffold.d.ts +22 -0
- package/dist/tools/identity-scaffold.js +142 -0
- package/dist/tools/identity.d.ts +4 -0
- package/dist/tools/identity.js +226 -0
- package/dist/tools/knowledge-archive.d.ts +5 -0
- package/dist/tools/knowledge-archive.js +24 -0
- package/dist/tools/knowledge-history.d.ts +6 -0
- package/dist/tools/knowledge-history.js +55 -0
- package/dist/tools/knowledge-maintain.d.ts +9 -0
- package/dist/tools/knowledge-maintain.js +113 -0
- package/dist/tools/knowledge-merge.d.ts +8 -0
- package/dist/tools/knowledge-merge.js +40 -0
- package/dist/tools/knowledge-move.d.ts +10 -0
- package/dist/tools/knowledge-move.js +48 -0
- package/dist/tools/knowledge-purge.d.ts +5 -0
- package/dist/tools/knowledge-purge.js +20 -0
- package/dist/tools/knowledge-recall.d.ts +20 -0
- package/dist/tools/knowledge-recall.js +142 -0
- package/dist/tools/knowledge-restore.d.ts +4 -0
- package/dist/tools/knowledge-restore.js +24 -0
- package/dist/tools/knowledge-supersede.d.ts +6 -0
- package/dist/tools/knowledge-supersede.js +36 -0
- package/dist/tools/knowledge-verify.d.ts +8 -0
- package/dist/tools/knowledge-verify.js +39 -0
- package/dist/tools/knowledge-write.d.ts +25 -0
- package/dist/tools/knowledge-write.js +87 -0
- package/dist/tools/memory-audit.d.ts +3 -0
- package/dist/tools/memory-audit.js +47 -0
- package/dist/tools/memory-list.d.ts +4 -0
- package/dist/tools/memory-list.js +23 -0
- package/dist/tools/prune.d.ts +4 -0
- package/dist/tools/prune.js +28 -0
- package/dist/tools/recall.d.ts +4 -0
- package/dist/tools/recall.js +24 -0
- package/dist/tools/remember.d.ts +10 -0
- package/dist/tools/remember.js +43 -0
- package/dist/tools/restore.d.ts +2 -0
- package/dist/tools/restore.js +20 -0
- package/dist/tools/update-identity.d.ts +34 -0
- package/dist/tools/update-identity.js +194 -0
- package/dist/tools/update.d.ts +2 -0
- package/dist/tools/update.js +16 -0
- package/dist/transport/guards.d.ts +35 -0
- package/dist/transport/guards.js +107 -0
- package/dist/transport/http-server.d.ts +17 -0
- package/dist/transport/http-server.js +198 -0
- package/dist/transport/mcp-app-hello.d.ts +3 -0
- package/dist/transport/mcp-app-hello.js +66 -0
- package/dist/widgets/display-modes.html +131 -0
- package/dist/widgets/hello.html +138 -0
- package/dist/widgets/proposals.html +131 -0
- package/dist/widgets/top-of-mind.html +131 -0
- package/package.json +73 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loom doctor — read-only environment probe. Reports node version
|
|
3
|
+
* compatibility, stack version, existing agents under
|
|
4
|
+
* ~/.config/loom/*, and forward-looking git fields per agent. Never
|
|
5
|
+
* writes. Exit 0 regardless of findings; health is the output, not
|
|
6
|
+
* the exit code.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArgs } from 'node:util';
|
|
9
|
+
import { readdir, readFile, stat, lstat, readlink } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { extractGlobalFlags } from './args.js';
|
|
12
|
+
import { renderJson } from './io.js';
|
|
13
|
+
import { CURRENT_STACK_VERSION, resolveDefaultContextPath } from '../config.js';
|
|
14
|
+
const USAGE = `Usage: loom doctor [options]
|
|
15
|
+
|
|
16
|
+
Probes the loom environment. Read-only; exits 0 regardless of findings.
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--json Machine-readable output
|
|
20
|
+
--help Show this help
|
|
21
|
+
`;
|
|
22
|
+
async function fileExists(p) {
|
|
23
|
+
try {
|
|
24
|
+
await stat(p);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function probeGit(agentDir) {
|
|
32
|
+
const dotGit = join(agentDir, '.git');
|
|
33
|
+
const initialized = await fileExists(dotGit);
|
|
34
|
+
const gitignorePresent = await fileExists(join(agentDir, '.gitignore'));
|
|
35
|
+
return {
|
|
36
|
+
initialized,
|
|
37
|
+
hasRemote: false,
|
|
38
|
+
dirty: false,
|
|
39
|
+
gitignorePresent,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function probeDefault(root) {
|
|
43
|
+
const defaultPath = join(root, 'default');
|
|
44
|
+
let s;
|
|
45
|
+
try {
|
|
46
|
+
s = await lstat(defaultPath);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { exists: false, isSymlink: false };
|
|
50
|
+
}
|
|
51
|
+
if (s.isSymbolicLink()) {
|
|
52
|
+
const target = await readlink(defaultPath);
|
|
53
|
+
try {
|
|
54
|
+
await stat(defaultPath); // follows the symlink; throws if dangling
|
|
55
|
+
const empty = !(await fileExists(join(defaultPath, 'IDENTITY.md')));
|
|
56
|
+
return { exists: true, isSymlink: true, target, dangling: false, empty };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { exists: true, isSymlink: true, target, dangling: true };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// A real directory named "default" — should be a symlink per the spec
|
|
63
|
+
const empty = !(await fileExists(join(defaultPath, 'IDENTITY.md')));
|
|
64
|
+
return { exists: true, isSymlink: false, empty };
|
|
65
|
+
}
|
|
66
|
+
async function probeAgents(home) {
|
|
67
|
+
const root = join(home, '.config', 'loom');
|
|
68
|
+
const agents = [];
|
|
69
|
+
let entries = [];
|
|
70
|
+
try {
|
|
71
|
+
entries = await readdir(root);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
const defaultStatus = await probeDefault(root);
|
|
75
|
+
return { root, agents, defaultStatus };
|
|
76
|
+
}
|
|
77
|
+
for (const name of entries.sort()) {
|
|
78
|
+
if (name === 'default')
|
|
79
|
+
continue; // handled via probeDefault
|
|
80
|
+
const p = join(root, name);
|
|
81
|
+
let s;
|
|
82
|
+
try {
|
|
83
|
+
s = await stat(p);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!s.isDirectory())
|
|
89
|
+
continue;
|
|
90
|
+
agents.push({
|
|
91
|
+
name,
|
|
92
|
+
path: p,
|
|
93
|
+
hasIdentity: await fileExists(join(p, 'IDENTITY.md')),
|
|
94
|
+
hasMemoriesDb: await fileExists(join(p, 'memories.db')),
|
|
95
|
+
git: await probeGit(p),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const defaultStatus = await probeDefault(root);
|
|
99
|
+
return { root, agents, defaultStatus };
|
|
100
|
+
}
|
|
101
|
+
function defaultIsOk(ds) {
|
|
102
|
+
if (!ds.exists)
|
|
103
|
+
return false;
|
|
104
|
+
if (!ds.isSymlink)
|
|
105
|
+
return false; // real dir is not ok — must be a symlink
|
|
106
|
+
if (ds.dangling)
|
|
107
|
+
return false;
|
|
108
|
+
if (ds.empty)
|
|
109
|
+
return false;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
function nodeOk(version) {
|
|
113
|
+
const m = version.match(/^v(\d+)\./);
|
|
114
|
+
return m !== null && Number(m[1]) >= 20;
|
|
115
|
+
}
|
|
116
|
+
async function probeStackVersion(contextDir) {
|
|
117
|
+
const file = join(contextDir, 'LOOM_STACK_VERSION');
|
|
118
|
+
try {
|
|
119
|
+
const body = (await readFile(file, 'utf-8')).trim();
|
|
120
|
+
const v = Number(body);
|
|
121
|
+
return Number.isInteger(v) && v <= CURRENT_STACK_VERSION;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export async function run(argv, io) {
|
|
128
|
+
const { flags: global, rest } = extractGlobalFlags(argv);
|
|
129
|
+
let parsed;
|
|
130
|
+
try {
|
|
131
|
+
parsed = parseArgs({
|
|
132
|
+
args: rest,
|
|
133
|
+
options: {
|
|
134
|
+
json: { type: 'boolean' },
|
|
135
|
+
help: { type: 'boolean', short: 'h' },
|
|
136
|
+
},
|
|
137
|
+
strict: true,
|
|
138
|
+
allowPositionals: false,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
io.stderr(`${err.message}\n${USAGE}`);
|
|
143
|
+
return 2;
|
|
144
|
+
}
|
|
145
|
+
if (parsed.values.help) {
|
|
146
|
+
io.stdout(USAGE);
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
const home = io.env.HOME ?? process.env.HOME ?? '';
|
|
150
|
+
const contextDir = global.contextDir ?? io.env.LOOM_CONTEXT_DIR ?? resolveDefaultContextPath(home);
|
|
151
|
+
const { root, agents, defaultStatus } = await probeAgents(home);
|
|
152
|
+
const report = {
|
|
153
|
+
nodeOk: nodeOk(process.version),
|
|
154
|
+
nodeVersion: process.version,
|
|
155
|
+
stackVersionOk: await probeStackVersion(contextDir),
|
|
156
|
+
contextDirResolved: contextDir,
|
|
157
|
+
agentsRoot: root,
|
|
158
|
+
existingAgents: agents,
|
|
159
|
+
defaultStatus,
|
|
160
|
+
defaultOk: defaultIsOk(defaultStatus),
|
|
161
|
+
};
|
|
162
|
+
const json = Boolean(parsed.values.json) || Boolean(global.json);
|
|
163
|
+
if (json) {
|
|
164
|
+
renderJson(io, report);
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
const lines = [];
|
|
168
|
+
lines.push(`node: ${report.nodeVersion}${report.nodeOk ? '' : ' (unsupported — need ≥ 20)'}`);
|
|
169
|
+
lines.push(`stack: ${report.stackVersionOk ? 'compatible' : 'incompatible'}`);
|
|
170
|
+
lines.push(`context dir: ${report.contextDirResolved}`);
|
|
171
|
+
lines.push(`agents root: ${report.agentsRoot}`);
|
|
172
|
+
const ds = report.defaultStatus;
|
|
173
|
+
if (!ds.exists) {
|
|
174
|
+
lines.push('default: missing [FAIL — run `loom bootstrap` to create an agent and set up the default pointer]');
|
|
175
|
+
}
|
|
176
|
+
else if (!ds.isSymlink) {
|
|
177
|
+
lines.push('default: real directory (should be a symlink) [FAIL]');
|
|
178
|
+
}
|
|
179
|
+
else if (ds.dangling) {
|
|
180
|
+
lines.push(`default: ${ds.target} (dangling — target does not exist) [FAIL]`);
|
|
181
|
+
}
|
|
182
|
+
else if (ds.empty) {
|
|
183
|
+
lines.push(`default: -> ${ds.target} (no IDENTITY.md) [FAIL]`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
lines.push(`default: -> ${ds.target} [ok]`);
|
|
187
|
+
}
|
|
188
|
+
if (report.existingAgents.length === 0) {
|
|
189
|
+
lines.push('agents: (none)');
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
lines.push(`agents: ${report.existingAgents.length}`);
|
|
193
|
+
for (const a of report.existingAgents) {
|
|
194
|
+
const flags = [];
|
|
195
|
+
if (a.hasIdentity)
|
|
196
|
+
flags.push('identity');
|
|
197
|
+
if (a.hasMemoriesDb)
|
|
198
|
+
flags.push('memories.db');
|
|
199
|
+
if (a.git.initialized)
|
|
200
|
+
flags.push('git');
|
|
201
|
+
lines.push(` - ${a.name} (${flags.join(', ') || 'empty'})`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
io.stdout(lines.join('\n') + '\n');
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loom forget — remove memories by ref, category+title, or title pattern.
|
|
3
|
+
*/
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
import { forget } from '../tools/forget.js';
|
|
6
|
+
import { createBackend } from '../backends/index.js';
|
|
7
|
+
import { assertStackVersionCompatible } from '../config.js';
|
|
8
|
+
import { extractGlobalFlags, resolveEnv } from './args.js';
|
|
9
|
+
import { renderJson } from './io.js';
|
|
10
|
+
const USAGE = `Usage:
|
|
11
|
+
loom forget <ref>
|
|
12
|
+
loom forget --category <cat> --title <exact>
|
|
13
|
+
loom forget --title-pattern <glob> (--category <cat> | --project <name>)
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--confirm Required for scope deletion (category/project/pattern
|
|
17
|
+
without an exact target). Without it, prints a preview
|
|
18
|
+
of what would be deleted and deletes nothing.
|
|
19
|
+
--json Emit ForgetResult
|
|
20
|
+
--context-dir <path> Agent context dir
|
|
21
|
+
--help, -h Show this help
|
|
22
|
+
`;
|
|
23
|
+
export async function run(argv, io) {
|
|
24
|
+
const { flags: global, rest } = extractGlobalFlags(argv);
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = parseArgs({
|
|
28
|
+
args: rest,
|
|
29
|
+
options: {
|
|
30
|
+
category: { type: 'string' },
|
|
31
|
+
title: { type: 'string' },
|
|
32
|
+
project: { type: 'string' },
|
|
33
|
+
'title-pattern': { type: 'string' },
|
|
34
|
+
confirm: { type: 'boolean' },
|
|
35
|
+
help: { type: 'boolean', short: 'h' },
|
|
36
|
+
},
|
|
37
|
+
strict: true,
|
|
38
|
+
allowPositionals: true,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
io.stderr(`${err.message}\n${USAGE}`);
|
|
43
|
+
return 2;
|
|
44
|
+
}
|
|
45
|
+
if (parsed.values.help) {
|
|
46
|
+
io.stdout(USAGE);
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
const input = {
|
|
50
|
+
ref: parsed.positionals[0],
|
|
51
|
+
category: parsed.values.category,
|
|
52
|
+
title: parsed.values.title,
|
|
53
|
+
project: parsed.values.project,
|
|
54
|
+
title_pattern: parsed.values['title-pattern'],
|
|
55
|
+
};
|
|
56
|
+
const confirm = parsed.values.confirm === true;
|
|
57
|
+
const isSingleTarget = Boolean(input.ref) || Boolean(input.category && input.title);
|
|
58
|
+
if (input.title_pattern && !input.category && !input.project) {
|
|
59
|
+
io.stderr(`--title-pattern requires --category or --project as a scope guard.\n`);
|
|
60
|
+
return 2;
|
|
61
|
+
}
|
|
62
|
+
const hasAny = input.ref || input.category || input.project || input.title_pattern;
|
|
63
|
+
if (!hasAny) {
|
|
64
|
+
io.stderr(`Nothing to forget.\n${USAGE}`);
|
|
65
|
+
return 2;
|
|
66
|
+
}
|
|
67
|
+
const env = resolveEnv(global, io.env);
|
|
68
|
+
try {
|
|
69
|
+
assertStackVersionCompatible(env.contextDir);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
io.stderr(`${err.message}\n`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
// Scope deletion ceremony holds at every surface (store-convergence
|
|
76
|
+
// §"One guard grammar") — the --json fast path must not bypass the gate.
|
|
77
|
+
if (!isSingleTarget && !confirm) {
|
|
78
|
+
const preview = await forget(env.contextDir, { ...input, confirm: false });
|
|
79
|
+
if (env.json) {
|
|
80
|
+
renderJson(io, { deleted: [], preview, confirmRequired: true });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
io.stdout(preview.endsWith('\n') ? preview : preview + '\n');
|
|
84
|
+
io.stdout('Re-run with --confirm to delete.\n');
|
|
85
|
+
}
|
|
86
|
+
return 2;
|
|
87
|
+
}
|
|
88
|
+
if (env.json) {
|
|
89
|
+
const backend = createBackend(env.contextDir);
|
|
90
|
+
const result = await backend.forget(input);
|
|
91
|
+
renderJson(io, result);
|
|
92
|
+
return result.deleted.length === 0 ? 3 : 0;
|
|
93
|
+
}
|
|
94
|
+
const text = await forget(env.contextDir, { ...input, confirm });
|
|
95
|
+
io.stdout(text.endsWith('\n') ? text : text + '\n');
|
|
96
|
+
return /not found|No memories matched/i.test(text) ? 3 : 0;
|
|
97
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
/**
|
|
3
|
+
* loom harness — manifest lifecycle for harness adapters.
|
|
4
|
+
*
|
|
5
|
+
* The only subcommand is `init`: writes a manifest template to
|
|
6
|
+
* <contextDir>/harnesses/<name>.md. Reading is implicit — identity()
|
|
7
|
+
* already composes the manifest from disk.
|
|
8
|
+
*/
|
|
9
|
+
import { parseArgs } from 'node:util';
|
|
10
|
+
import { assertStackVersionCompatible } from '../config.js';
|
|
11
|
+
import { extractGlobalFlags, resolveEnv } from './args.js';
|
|
12
|
+
import { renderJson } from './io.js';
|
|
13
|
+
import { initHarness } from '../blocks/harness.js';
|
|
14
|
+
const USAGE = `Usage: loom harness <subcommand> [options]
|
|
15
|
+
|
|
16
|
+
Subcommands:
|
|
17
|
+
init [<name>] Write a manifest template for <name>
|
|
18
|
+
|
|
19
|
+
Options (init):
|
|
20
|
+
--force Overwrite existing manifest
|
|
21
|
+
--json Emit InitResult as JSON
|
|
22
|
+
|
|
23
|
+
<name> falls back to --client, then $LOOM_CLIENT.
|
|
24
|
+
|
|
25
|
+
Global: --context-dir, --client, --json, --help
|
|
26
|
+
`;
|
|
27
|
+
export async function run(argv, io) {
|
|
28
|
+
const { flags: global, rest } = extractGlobalFlags(argv);
|
|
29
|
+
const sub = rest[0];
|
|
30
|
+
const subRest = rest.slice(1);
|
|
31
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
32
|
+
io.stdout(USAGE);
|
|
33
|
+
return sub ? 0 : 2;
|
|
34
|
+
}
|
|
35
|
+
if (sub !== 'init') {
|
|
36
|
+
io.stderr(`Unknown harness subcommand: ${sub}\n${USAGE}`);
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
const env = resolveEnv(global, io.env);
|
|
40
|
+
try {
|
|
41
|
+
assertStackVersionCompatible(env.contextDir);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
io.stderr(`${err.message}\n`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = parseArgs({
|
|
50
|
+
args: subRest,
|
|
51
|
+
options: {
|
|
52
|
+
force: { type: 'boolean' },
|
|
53
|
+
help: { type: 'boolean', short: 'h' },
|
|
54
|
+
},
|
|
55
|
+
strict: true,
|
|
56
|
+
allowPositionals: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
io.stderr(`${err.message}\n${USAGE}`);
|
|
61
|
+
return 2;
|
|
62
|
+
}
|
|
63
|
+
if (parsed.values.help) {
|
|
64
|
+
io.stdout(USAGE);
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
const name = parsed.positionals[0] ?? env.client;
|
|
68
|
+
if (!name) {
|
|
69
|
+
io.stderr('loom harness init: <name> required (or pass --client / set $LOOM_CLIENT)\n');
|
|
70
|
+
return 2;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const result = await initHarness(env.contextDir, name, {
|
|
74
|
+
overwrite: parsed.values.force === true,
|
|
75
|
+
});
|
|
76
|
+
if (env.json) {
|
|
77
|
+
renderJson(io, result);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
io.stdout(`${result.name}: ${result.path} (${result.action})\n`);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
if (err.message.startsWith('Invalid harness name')) {
|
|
85
|
+
io.stderr(`${err.message}\n`);
|
|
86
|
+
return 2;
|
|
87
|
+
}
|
|
88
|
+
io.stderr(`loom harness init: ${err.message}\n`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI dispatcher — top-level --help / --version plus subcommand routing.
|
|
3
|
+
* Individual subcommand files land in Tasks 2–10.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { resolveRepoRoot } from '../config.js';
|
|
8
|
+
import { realStreams } from './io.js';
|
|
9
|
+
import { SUBCOMMANDS } from './subcommands.js';
|
|
10
|
+
const TOP_HELP = `Usage: loom <command> [options]
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
wake Print agent identity to stdout
|
|
14
|
+
recall <query> Search memories
|
|
15
|
+
remember <title> Save a new memory (body via stdin/$EDITOR)
|
|
16
|
+
update <ref> Modify an existing memory
|
|
17
|
+
forget <ref|scope> Remove memories
|
|
18
|
+
memory list|prune|similar|audit
|
|
19
|
+
Browse / clean / find duplicates / health report
|
|
20
|
+
knowledge write|recall|maintain
|
|
21
|
+
Write / search / health-report the knowledge store
|
|
22
|
+
update-identity Edit preferences.md / self-model.md sections
|
|
23
|
+
bootstrap Initialize a fresh agent
|
|
24
|
+
serve Explicit MCP stdio startup (same as no args)
|
|
25
|
+
inject Write loom identity pointer into harness dotfiles
|
|
26
|
+
harness init Scaffold a harness manifest from template
|
|
27
|
+
install Write loom-setup skill into a harness's skills dir
|
|
28
|
+
doctor Probe the loom environment (read-only)
|
|
29
|
+
migrate Apply pending schema migrations to memories.db
|
|
30
|
+
|
|
31
|
+
Global flags:
|
|
32
|
+
--context-dir <path> Agent context dir (default: $LOOM_CONTEXT_DIR or ~/.config/loom/default)
|
|
33
|
+
--client <name> Harness adapter hint (default: $LOOM_CLIENT)
|
|
34
|
+
--model <name> Model manifest hint (default: $LOOM_MODEL)
|
|
35
|
+
--json Machine-readable output
|
|
36
|
+
--help, -h Show help
|
|
37
|
+
--version, -V Print loom version
|
|
38
|
+
|
|
39
|
+
Run 'loom <command> --help' for per-command usage.
|
|
40
|
+
`;
|
|
41
|
+
async function readVersion() {
|
|
42
|
+
const pkg = JSON.parse(await readFile(join(resolveRepoRoot(), 'package.json'), 'utf-8'));
|
|
43
|
+
return pkg.version;
|
|
44
|
+
}
|
|
45
|
+
export async function runCli(argv, io = realStreams()) {
|
|
46
|
+
const first = argv[0];
|
|
47
|
+
if (first === '--help' || first === '-h' || first === undefined) {
|
|
48
|
+
io.stdout(TOP_HELP);
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
if (first === '--version' || first === '-V') {
|
|
52
|
+
io.stdout(`loom v${await readVersion()}\n`);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
if (!SUBCOMMANDS.includes(first)) {
|
|
56
|
+
io.stderr(`Unknown subcommand: ${first}\n`);
|
|
57
|
+
io.stderr(TOP_HELP);
|
|
58
|
+
return 2;
|
|
59
|
+
}
|
|
60
|
+
const sub = first;
|
|
61
|
+
const rest = argv.slice(1);
|
|
62
|
+
switch (sub) {
|
|
63
|
+
case 'wake': {
|
|
64
|
+
const { run } = await import('./wake.js');
|
|
65
|
+
return run(rest, io);
|
|
66
|
+
}
|
|
67
|
+
case 'recall': {
|
|
68
|
+
const { run } = await import('./recall.js');
|
|
69
|
+
return run(rest, io);
|
|
70
|
+
}
|
|
71
|
+
case 'memory': {
|
|
72
|
+
const { run } = await import('./memory.js');
|
|
73
|
+
return run(rest, io);
|
|
74
|
+
}
|
|
75
|
+
case 'knowledge': {
|
|
76
|
+
const { run } = await import('./knowledge.js');
|
|
77
|
+
return run(rest, io);
|
|
78
|
+
}
|
|
79
|
+
case 'forget': {
|
|
80
|
+
const { run } = await import('./forget.js');
|
|
81
|
+
return run(rest, io);
|
|
82
|
+
}
|
|
83
|
+
case 'remember': {
|
|
84
|
+
const { run } = await import('./remember.js');
|
|
85
|
+
return run(rest, io);
|
|
86
|
+
}
|
|
87
|
+
case 'update': {
|
|
88
|
+
const { run } = await import('./update.js');
|
|
89
|
+
return run(rest, io);
|
|
90
|
+
}
|
|
91
|
+
case 'bootstrap': {
|
|
92
|
+
const { run } = await import('./bootstrap.js');
|
|
93
|
+
return run(rest, io);
|
|
94
|
+
}
|
|
95
|
+
case 'serve': {
|
|
96
|
+
const { run } = await import('./serve.js');
|
|
97
|
+
return run(rest, io);
|
|
98
|
+
}
|
|
99
|
+
case 'update-identity': {
|
|
100
|
+
const { run } = await import('./update-identity.js');
|
|
101
|
+
return run(rest, io);
|
|
102
|
+
}
|
|
103
|
+
case 'inject': {
|
|
104
|
+
const { run } = await import('./inject.js');
|
|
105
|
+
return run(rest, io);
|
|
106
|
+
}
|
|
107
|
+
case 'harness': {
|
|
108
|
+
const { run } = await import('./harness.js');
|
|
109
|
+
return run(rest, io);
|
|
110
|
+
}
|
|
111
|
+
case 'install': {
|
|
112
|
+
const { run } = await import('./install.js');
|
|
113
|
+
return run(rest, io);
|
|
114
|
+
}
|
|
115
|
+
case 'doctor': {
|
|
116
|
+
const { run } = await import('./doctor.js');
|
|
117
|
+
return run(rest, io);
|
|
118
|
+
}
|
|
119
|
+
case 'migrate': {
|
|
120
|
+
const { run } = await import('./migrate.js');
|
|
121
|
+
return run(rest, io);
|
|
122
|
+
}
|
|
123
|
+
default:
|
|
124
|
+
io.stderr(`Subcommand not implemented yet: ${sub}\n`);
|
|
125
|
+
return 2;
|
|
126
|
+
}
|
|
127
|
+
}
|