jawcode 0.1.0 → 1.0.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/defaults/cli-jaw/settings.json +128 -0
- package/dist/jwc.bundle.js +58352 -64725
- package/dist/sync-worker.js +258016 -0
- package/dist-node/sdk.js +25023 -32647
- package/package.json +8 -5
- package/scripts/bootstrap-cli-jaw-home.cjs +383 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jawcode",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"description": "Jawcode (jwc) — structured coding-agent runtime CLI for cli-jaw and standalone use",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -24,21 +24,24 @@
|
|
|
24
24
|
"bin",
|
|
25
25
|
"dist",
|
|
26
26
|
"dist-node",
|
|
27
|
+
"defaults/cli-jaw/settings.json",
|
|
27
28
|
"scripts/resolve-bun-runtime.cjs",
|
|
28
29
|
"scripts/verify-runtime.cjs",
|
|
30
|
+
"scripts/bootstrap-cli-jaw-home.cjs",
|
|
29
31
|
"src/index.ts",
|
|
30
32
|
"src/sdk.ts"
|
|
31
33
|
],
|
|
32
34
|
"scripts": {
|
|
33
|
-
"bundle": "bun build src/cli-entry.ts --target=bun --outfile=dist/jwc.bundle.js --external mupdf --external markit-ai --external @gajae-code/natives",
|
|
35
|
+
"bundle": "bun build src/cli-entry.ts --target=bun --outfile=dist/jwc.bundle.js --external mupdf --external markit-ai --external @gajae-code/natives && bun build ../stats/src/sync-worker.ts --target=bun --outfile=dist/sync-worker.js --external @gajae-code/natives",
|
|
34
36
|
"build:node": "bun scripts/build-node.ts",
|
|
35
|
-
"postinstall": "node scripts/verify-runtime.cjs --postinstall",
|
|
37
|
+
"postinstall": "node scripts/verify-runtime.cjs --postinstall && node scripts/bootstrap-cli-jaw-home.cjs --postinstall",
|
|
38
|
+
"bootstrap:cli-jaw": "node scripts/bootstrap-cli-jaw-home.cjs",
|
|
36
39
|
"verify:runtime": "node scripts/verify-runtime.cjs",
|
|
37
40
|
"smoke:node-sdk": "node scripts/smoke-node-sdk.mjs",
|
|
38
41
|
"smoke:packed-sdk": "node scripts/smoke-packed-sdk.mjs"
|
|
39
42
|
},
|
|
40
43
|
"dependencies": {
|
|
41
|
-
"@gajae-code/natives": "0.
|
|
44
|
+
"@gajae-code/natives": "1.0.0",
|
|
42
45
|
"markit-ai": "0.5.3",
|
|
43
46
|
"better-sqlite3": "^11.10.0",
|
|
44
47
|
"json5": "^2.2.3",
|
|
@@ -47,7 +50,7 @@
|
|
|
47
50
|
"bun": "1.3.14"
|
|
48
51
|
},
|
|
49
52
|
"devDependencies": {
|
|
50
|
-
"@gajae-code/coding-agent": "0.
|
|
53
|
+
"@gajae-code/coding-agent": "1.0.0",
|
|
51
54
|
"esbuild": "^0.25.0",
|
|
52
55
|
"yaml": "^2.6.0",
|
|
53
56
|
"smol-toml": "^1.3.0",
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const childProcess = require("node:child_process");
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const os = require("node:os");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
|
|
8
|
+
const DEFAULT_SKILLS_REPO = "https://github.com/lidge-jun/cli-jaw-skills.git";
|
|
9
|
+
const CLONE_COOLDOWN_MS = 10 * 60 * 1000;
|
|
10
|
+
const CLONE_TIMEOUT_MS = 80_000;
|
|
11
|
+
const BASE_AUTO_ACTIVATE = new Set([
|
|
12
|
+
"pdf",
|
|
13
|
+
"browser",
|
|
14
|
+
"memory",
|
|
15
|
+
"screen-capture",
|
|
16
|
+
"docx",
|
|
17
|
+
"xlsx",
|
|
18
|
+
"pptx",
|
|
19
|
+
"hwp",
|
|
20
|
+
"github",
|
|
21
|
+
"telegram-send",
|
|
22
|
+
"video",
|
|
23
|
+
"pdf-vision",
|
|
24
|
+
"diagram",
|
|
25
|
+
"desktop-control",
|
|
26
|
+
]);
|
|
27
|
+
const IGNORED_DIRS = new Set([".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build", ".next", ".turbo"]);
|
|
28
|
+
const IGNORED_FILES = new Set([".DS_Store"]);
|
|
29
|
+
|
|
30
|
+
const args = new Set(process.argv.slice(2));
|
|
31
|
+
const asJson = args.has("--json");
|
|
32
|
+
const checkOnly = args.has("--check");
|
|
33
|
+
const postinstall = args.has("--postinstall");
|
|
34
|
+
const safeMode = process.env.CI === "true" || process.env.JWC_SAFE === "1";
|
|
35
|
+
const disabled = process.env.JWC_SKIP_CLI_JAW_BOOTSTRAP === "1";
|
|
36
|
+
const packageRoot = path.join(__dirname, "..");
|
|
37
|
+
const targetHome = resolveCliJawHome(process.env, os.homedir());
|
|
38
|
+
const activeDir = path.join(targetHome, "skills");
|
|
39
|
+
const refDir = path.join(targetHome, "skills_ref");
|
|
40
|
+
const cloneMetaPath = path.join(targetHome, ".skills_clone_meta.json");
|
|
41
|
+
|
|
42
|
+
function expandHome(value, homeDir) {
|
|
43
|
+
if (value === "~") return homeDir;
|
|
44
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(homeDir, value.slice(2));
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveCliJawHome(env = process.env, homeDir = os.homedir()) {
|
|
49
|
+
const configured = env.CLI_JAW_HOME?.trim();
|
|
50
|
+
const raw = configured && configured.length > 0 ? configured : path.join(homeDir, ".cli-jaw");
|
|
51
|
+
return path.resolve(expandHome(raw, homeDir));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function status(pathValue, status) {
|
|
55
|
+
return { path: pathValue, status };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function makeResult() {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
targetHome,
|
|
62
|
+
safeMode,
|
|
63
|
+
postinstall,
|
|
64
|
+
settings: status(path.join(targetHome, "settings.json"), "pending"),
|
|
65
|
+
heartbeat: status(path.join(targetHome, "heartbeat.json"), "pending"),
|
|
66
|
+
mcp: status(path.join(targetHome, "mcp.json"), "pending"),
|
|
67
|
+
skills: {
|
|
68
|
+
activeDir,
|
|
69
|
+
refDir,
|
|
70
|
+
status: "pending",
|
|
71
|
+
source: "none",
|
|
72
|
+
downloaded: false,
|
|
73
|
+
activeCount: 0,
|
|
74
|
+
refCount: 0,
|
|
75
|
+
ignoredDirNames: [...IGNORED_DIRS],
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ensureDir(dir) {
|
|
81
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function writeIfMissing(filePath, content) {
|
|
85
|
+
if (fs.existsSync(filePath)) return "exists";
|
|
86
|
+
ensureDir(path.dirname(filePath));
|
|
87
|
+
fs.writeFileSync(filePath, content);
|
|
88
|
+
return "written";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readSettingsSeed() {
|
|
92
|
+
const seedPath = path.join(packageRoot, "defaults", "cli-jaw", "settings.json");
|
|
93
|
+
const text = fs.readFileSync(seedPath, "utf8").replaceAll("__CLI_JAW_HOME__", targetHome.replace(/\\/g, "\\\\"));
|
|
94
|
+
JSON.parse(text);
|
|
95
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function ensureBaseHome(result, mode) {
|
|
99
|
+
ensureDir(targetHome);
|
|
100
|
+
if (mode === "home-only") {
|
|
101
|
+
result.settings.status = "skipped";
|
|
102
|
+
result.heartbeat.status = "skipped";
|
|
103
|
+
result.mcp.status = "skipped";
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
ensureDir(path.join(targetHome, "uploads"));
|
|
107
|
+
ensureDir(path.join(targetHome, "prompts"));
|
|
108
|
+
result.settings.status = writeIfMissing(result.settings.path, readSettingsSeed());
|
|
109
|
+
result.heartbeat.status = writeIfMissing(result.heartbeat.path, `${JSON.stringify({ jobs: [] }, null, "\t")}\n`);
|
|
110
|
+
result.mcp.status = writeIfMissing(result.mcp.path, `${JSON.stringify({ servers: {} }, null, "\t")}\n`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readJson(filePath, fallback) {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
116
|
+
} catch {
|
|
117
|
+
return fallback;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function loadRegistry(dir) {
|
|
122
|
+
return readJson(path.join(dir, "registry.json"), { skills: {} });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function countSkillDirs(dir) {
|
|
126
|
+
try {
|
|
127
|
+
return fs.readdirSync(dir, { withFileTypes: true }).filter(entry => entry.isDirectory() && isSkillDir(entry.name)).length;
|
|
128
|
+
} catch {
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isSkillDir(name) {
|
|
134
|
+
return !name.startsWith(".") && !name.endsWith(".bak") && !name.endsWith("_original") && !IGNORED_DIRS.has(name);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function semverGt(leftValue, rightValue) {
|
|
138
|
+
const left = String(leftValue ?? "").split(".").slice(0, 3).map(part => Number.parseInt(part, 10) || 0);
|
|
139
|
+
const right = String(rightValue ?? "").split(".").slice(0, 3).map(part => Number.parseInt(part, 10) || 0);
|
|
140
|
+
for (let index = 0; index < 3; index++) {
|
|
141
|
+
if (left[index] > right[index]) return true;
|
|
142
|
+
if (left[index] < right[index]) return false;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function updateHash(hash, current, root) {
|
|
148
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
149
|
+
if (entry.isDirectory() && IGNORED_DIRS.has(entry.name)) continue;
|
|
150
|
+
if (entry.isFile() && (IGNORED_FILES.has(entry.name) || entry.name.endsWith(".pyc"))) continue;
|
|
151
|
+
if (entry.name.startsWith(".") && entry.name !== ".well-known") continue;
|
|
152
|
+
const entryPath = path.join(current, entry.name);
|
|
153
|
+
if (entry.isDirectory()) {
|
|
154
|
+
updateHash(hash, entryPath, root);
|
|
155
|
+
} else if (entry.isFile()) {
|
|
156
|
+
hash.update(path.relative(root, entryPath).replace(/\\/g, "/"));
|
|
157
|
+
hash.update("\0");
|
|
158
|
+
hash.update(fs.readFileSync(entryPath));
|
|
159
|
+
hash.update("\0");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function fingerprint(dir) {
|
|
165
|
+
const hash = crypto.createHash("sha256");
|
|
166
|
+
updateHash(hash, dir, dir);
|
|
167
|
+
return hash.digest("hex");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function shouldUpdateSkill(id, src, dst, srcRegistry, dstRegistry) {
|
|
171
|
+
if (!fs.existsSync(dst)) return true;
|
|
172
|
+
const srcVersion = srcRegistry.skills?.[id]?.version;
|
|
173
|
+
const dstVersion = dstRegistry.skills?.[id]?.version;
|
|
174
|
+
if (srcVersion && (!dstVersion || semverGt(srcVersion, dstVersion))) return true;
|
|
175
|
+
return fingerprint(src) !== fingerprint(dst);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function copyTree(src, dst) {
|
|
179
|
+
ensureDir(dst);
|
|
180
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
181
|
+
if (entry.isDirectory() && IGNORED_DIRS.has(entry.name)) continue;
|
|
182
|
+
if (entry.isFile() && (IGNORED_FILES.has(entry.name) || entry.name.endsWith(".pyc"))) continue;
|
|
183
|
+
const srcPath = path.join(src, entry.name);
|
|
184
|
+
const dstPath = path.join(dst, entry.name);
|
|
185
|
+
if (entry.isDirectory()) copyTree(srcPath, dstPath);
|
|
186
|
+
else if (entry.isFile()) fs.copyFileSync(srcPath, dstPath);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function mergeRef(sourceRoot) {
|
|
191
|
+
ensureDir(refDir);
|
|
192
|
+
const srcRegistry = loadRegistry(sourceRoot);
|
|
193
|
+
const dstRegistry = loadRegistry(refDir);
|
|
194
|
+
for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
195
|
+
if (entry.name === ".git") continue;
|
|
196
|
+
const srcPath = path.join(sourceRoot, entry.name);
|
|
197
|
+
const dstPath = path.join(refDir, entry.name);
|
|
198
|
+
if (entry.isDirectory()) {
|
|
199
|
+
if (!isSkillDir(entry.name)) continue;
|
|
200
|
+
if (shouldUpdateSkill(entry.name, srcPath, dstPath, srcRegistry, dstRegistry)) {
|
|
201
|
+
fs.rmSync(dstPath, { recursive: true, force: true });
|
|
202
|
+
copyTree(srcPath, dstPath);
|
|
203
|
+
}
|
|
204
|
+
} else if (entry.isFile() && !IGNORED_FILES.has(entry.name) && !entry.name.endsWith(".pyc")) {
|
|
205
|
+
fs.copyFileSync(srcPath, dstPath);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildAutoActivateSet() {
|
|
211
|
+
const registry = loadRegistry(refDir);
|
|
212
|
+
const out = new Set(BASE_AUTO_ACTIVATE);
|
|
213
|
+
for (const [id, meta] of Object.entries(registry.skills ?? {})) {
|
|
214
|
+
if (meta && meta.category === "orchestration") out.add(id);
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function activateSkills() {
|
|
220
|
+
const autoActivate = buildAutoActivateSet();
|
|
221
|
+
const registry = loadRegistry(refDir);
|
|
222
|
+
let activeCount = 0;
|
|
223
|
+
for (const id of autoActivate) {
|
|
224
|
+
const src = path.join(refDir, id);
|
|
225
|
+
const dst = path.join(activeDir, id);
|
|
226
|
+
if (!fs.existsSync(src)) continue;
|
|
227
|
+
ensureDir(activeDir);
|
|
228
|
+
if (shouldUpdateSkill(id, src, dst, registry, { skills: {} })) {
|
|
229
|
+
fs.rmSync(dst, { recursive: true, force: true });
|
|
230
|
+
copyTree(src, dst);
|
|
231
|
+
}
|
|
232
|
+
activeCount++;
|
|
233
|
+
}
|
|
234
|
+
return activeCount;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function readCloneMeta() {
|
|
238
|
+
const meta = readJson(cloneMetaPath, null);
|
|
239
|
+
return meta && typeof meta.lastAttempt === "number" && typeof meta.success === "boolean" ? meta : null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function writeCloneMeta(success) {
|
|
243
|
+
fs.writeFileSync(cloneMetaPath, `${JSON.stringify({ lastAttempt: Date.now(), success }, null, "\t")}\n`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function cloneCooldownActive() {
|
|
247
|
+
if (process.env.JWC_FORCE_CLI_JAW_SKILLS === "1" || process.env.JAW_FORCE_CLONE === "1") return false;
|
|
248
|
+
const meta = readCloneMeta();
|
|
249
|
+
return Boolean(meta && !meta.success && fs.existsSync(path.join(refDir, "registry.json")) && Date.now() - meta.lastAttempt < CLONE_COOLDOWN_MS);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function cloneSkillsRepo() {
|
|
253
|
+
if (cloneCooldownActive()) return undefined;
|
|
254
|
+
const repo = process.env.JWC_CLI_JAW_SKILLS_REPO || DEFAULT_SKILLS_REPO;
|
|
255
|
+
const tmp = path.join(targetHome, ".skills_clone_tmp");
|
|
256
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
257
|
+
try {
|
|
258
|
+
childProcess.execFileSync("git", ["clone", "--depth", "1", repo, tmp], { stdio: "ignore", timeout: CLONE_TIMEOUT_MS });
|
|
259
|
+
writeCloneMeta(true);
|
|
260
|
+
return tmp;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
writeCloneMeta(false);
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function bootstrapSkills(result) {
|
|
268
|
+
if (safeMode) {
|
|
269
|
+
const hasExistingRef = fs.existsSync(path.join(refDir, "registry.json"));
|
|
270
|
+
result.skills.status = "safe-skipped";
|
|
271
|
+
result.skills.source = hasExistingRef ? "existing" : "none";
|
|
272
|
+
if (hasExistingRef) {
|
|
273
|
+
result.skills.activeCount = activateSkills();
|
|
274
|
+
result.skills.refCount = countSkillDirs(refDir);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const localSource = process.env.JWC_CLI_JAW_SKILLS_SOURCE_DIR;
|
|
279
|
+
let sourceRoot;
|
|
280
|
+
let sourceKind = "none";
|
|
281
|
+
let tmpClone;
|
|
282
|
+
try {
|
|
283
|
+
if (localSource) {
|
|
284
|
+
sourceRoot = path.resolve(expandHome(localSource, os.homedir()));
|
|
285
|
+
sourceKind = "local-fixture";
|
|
286
|
+
} else {
|
|
287
|
+
tmpClone = cloneSkillsRepo();
|
|
288
|
+
if (tmpClone) {
|
|
289
|
+
sourceRoot = tmpClone;
|
|
290
|
+
sourceKind = "github";
|
|
291
|
+
result.skills.downloaded = true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (sourceRoot && fs.existsSync(sourceRoot)) {
|
|
295
|
+
mergeRef(sourceRoot);
|
|
296
|
+
result.skills.status = "written";
|
|
297
|
+
result.skills.source = sourceKind;
|
|
298
|
+
} else if (fs.existsSync(path.join(refDir, "registry.json"))) {
|
|
299
|
+
result.skills.status = "exists";
|
|
300
|
+
result.skills.source = "existing";
|
|
301
|
+
} else {
|
|
302
|
+
result.skills.status = "skipped";
|
|
303
|
+
result.skills.source = "none";
|
|
304
|
+
}
|
|
305
|
+
result.skills.activeCount = activateSkills();
|
|
306
|
+
result.skills.refCount = countSkillDirs(refDir);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
result.skills.status = postinstall ? "failed-nonfatal" : "failed";
|
|
309
|
+
result.skills.error = error instanceof Error ? error.message : String(error);
|
|
310
|
+
result.skills.source = fs.existsSync(path.join(refDir, "registry.json")) ? "existing" : sourceKind;
|
|
311
|
+
if (result.skills.source === "existing") {
|
|
312
|
+
result.skills.activeCount = activateSkills();
|
|
313
|
+
result.skills.refCount = countSkillDirs(refDir);
|
|
314
|
+
}
|
|
315
|
+
} finally {
|
|
316
|
+
if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function checkResult(result) {
|
|
321
|
+
result.settings.status = fs.existsSync(result.settings.path) ? "exists" : result.settings.status;
|
|
322
|
+
result.heartbeat.status = fs.existsSync(result.heartbeat.path) ? "exists" : result.heartbeat.status;
|
|
323
|
+
result.mcp.status = fs.existsSync(result.mcp.path) ? "exists" : result.mcp.status;
|
|
324
|
+
result.skills.refCount = countSkillDirs(refDir);
|
|
325
|
+
result.skills.activeCount = countSkillDirs(activeDir);
|
|
326
|
+
if (fs.existsSync(path.join(refDir, "registry.json"))) result.skills.source = "existing";
|
|
327
|
+
if (result.skills.status === "pending") result.skills.status = result.skills.refCount > 0 ? "exists" : "missing";
|
|
328
|
+
result.ok = fs.existsSync(result.settings.path) && fs.existsSync(result.heartbeat.path) && fs.existsSync(result.mcp.path) && fs.existsSync(path.join(refDir, "registry.json")) && result.skills.activeCount > 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function run() {
|
|
332
|
+
const result = makeResult();
|
|
333
|
+
if (disabled) {
|
|
334
|
+
if (!checkOnly && postinstall) ensureBaseHome(result, "home-only");
|
|
335
|
+
result.skills.status = "skipped";
|
|
336
|
+
result.skills.source = "none";
|
|
337
|
+
if (checkOnly) {
|
|
338
|
+
checkResult(result);
|
|
339
|
+
result.ok = false;
|
|
340
|
+
result.status = "skipped-not-ready";
|
|
341
|
+
} else {
|
|
342
|
+
result.ok = postinstall;
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
if (checkOnly) {
|
|
347
|
+
checkResult(result);
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
ensureBaseHome(result, "normal");
|
|
351
|
+
bootstrapSkills(result);
|
|
352
|
+
checkResult(result);
|
|
353
|
+
if (postinstall && result.skills.status === "failed-nonfatal") result.ok = true;
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function printHuman(result) {
|
|
358
|
+
const prefix = postinstall ? "[jwc:init]" : "jwc cli-jaw bootstrap";
|
|
359
|
+
if (result.ok) {
|
|
360
|
+
process.stdout.write(`${prefix} cli-jaw home ready: ${result.targetHome}\n`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const message = result.skills.error ? ` (${result.skills.error})` : "";
|
|
364
|
+
const text = `${prefix} cli-jaw home not fully ready: skills=${result.skills.status}${message}\n`;
|
|
365
|
+
(postinstall ? process.stdout : process.stderr).write(text);
|
|
366
|
+
if (postinstall) {
|
|
367
|
+
process.stdout.write(`${prefix} remediation: run \`npm rebuild jawcode\` or \`node ${__filename} --check --json\` from the installed package.\n`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let result;
|
|
372
|
+
try {
|
|
373
|
+
result = run();
|
|
374
|
+
} catch (error) {
|
|
375
|
+
result = makeResult();
|
|
376
|
+
result.ok = false;
|
|
377
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
378
|
+
if (postinstall) result.ok = true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (asJson) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
382
|
+
else printHuman(result);
|
|
383
|
+
process.exit(result.ok || postinstall ? 0 : 1);
|