@headways/cli 1.0.0 → 1.1.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/dist/index.js +137 -49
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,52 +18,6 @@ import {
|
|
|
18
18
|
import "dotenv/config";
|
|
19
19
|
import { program } from "commander";
|
|
20
20
|
|
|
21
|
-
// package.json
|
|
22
|
-
var package_default = {
|
|
23
|
-
name: "@headways/cli",
|
|
24
|
-
version: "1.0.0",
|
|
25
|
-
type: "module",
|
|
26
|
-
description: "Headways CLI \u2014 authoring, sync, and runtime SDK",
|
|
27
|
-
license: "MIT",
|
|
28
|
-
files: [
|
|
29
|
-
"dist",
|
|
30
|
-
"LICENSE",
|
|
31
|
-
"README.md"
|
|
32
|
-
],
|
|
33
|
-
bin: {
|
|
34
|
-
headways: "./dist/index.js"
|
|
35
|
-
},
|
|
36
|
-
publishConfig: {
|
|
37
|
-
access: "public"
|
|
38
|
-
},
|
|
39
|
-
scripts: {
|
|
40
|
-
build: "tsup",
|
|
41
|
-
dev: "tsx src/index.ts",
|
|
42
|
-
test: "vitest run",
|
|
43
|
-
"test:unit": "vitest run",
|
|
44
|
-
"type-check": "tsc -p tsconfig.json --noEmit",
|
|
45
|
-
prepublishOnly: "pnpm build"
|
|
46
|
-
},
|
|
47
|
-
dependencies: {
|
|
48
|
-
"@headways/db": "workspace:*",
|
|
49
|
-
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
50
|
-
chalk: "^5.3.0",
|
|
51
|
-
commander: "^12.1.0",
|
|
52
|
-
dotenv: "^16.4.7",
|
|
53
|
-
"node-fetch": "^3.3.2",
|
|
54
|
-
yaml: "^2.5.1",
|
|
55
|
-
zod: "^3.25.28"
|
|
56
|
-
},
|
|
57
|
-
devDependencies: {
|
|
58
|
-
"@headways/config": "workspace:*",
|
|
59
|
-
"@types/node": "^22.16.5",
|
|
60
|
-
tsup: "^8.5.1",
|
|
61
|
-
tsx: "^4.21.0",
|
|
62
|
-
typescript: "^5.8.3",
|
|
63
|
-
vitest: "^3.2.4"
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
21
|
// src/commands/auth.ts
|
|
68
22
|
import "commander";
|
|
69
23
|
import * as http from "http";
|
|
@@ -366,6 +320,7 @@ import "commander";
|
|
|
366
320
|
import * as fs2 from "fs/promises";
|
|
367
321
|
import * as path2 from "path";
|
|
368
322
|
import { watch, existsSync } from "fs";
|
|
323
|
+
import * as YAML from "yaml";
|
|
369
324
|
var RESERVED_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
370
325
|
"SKILL.md",
|
|
371
326
|
"headways.yaml",
|
|
@@ -426,12 +381,32 @@ async function readSkillDir(dir) {
|
|
|
426
381
|
const items = parseConnectionsYaml(connectionsYaml);
|
|
427
382
|
if (items.length > 0) connections = items;
|
|
428
383
|
}
|
|
384
|
+
let capabilities;
|
|
385
|
+
if (capabilitiesYaml) {
|
|
386
|
+
try {
|
|
387
|
+
capabilities = YAML.parse(capabilitiesYaml);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
`Failed to parse capabilities.yaml: ${err instanceof Error ? err.message : String(err)}`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
let hooks;
|
|
395
|
+
if (hooksYaml) {
|
|
396
|
+
try {
|
|
397
|
+
hooks = YAML.parse(hooksYaml);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
`Failed to parse hooks.yaml: ${err instanceof Error ? err.message : String(err)}`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
429
404
|
const extraFiles = await collectExtraFiles(dir);
|
|
430
405
|
return {
|
|
431
406
|
body,
|
|
432
407
|
headline,
|
|
433
|
-
capabilities
|
|
434
|
-
hooks
|
|
408
|
+
capabilities,
|
|
409
|
+
hooks,
|
|
435
410
|
connections,
|
|
436
411
|
files: Object.keys(extraFiles).length > 0 ? extraFiles : void 0
|
|
437
412
|
};
|
|
@@ -1115,12 +1090,125 @@ function getInstalledSkills() {
|
|
|
1115
1090
|
}
|
|
1116
1091
|
}
|
|
1117
1092
|
|
|
1093
|
+
// src/commands/upgrade.ts
|
|
1094
|
+
import { chmodSync, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1095
|
+
import { delimiter, basename as basename3, dirname, join as join6 } from "path";
|
|
1096
|
+
import { existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
1097
|
+
import "commander";
|
|
1098
|
+
var GCS_BASE = "https://storage.googleapis.com/headways-releases/cli";
|
|
1099
|
+
var NPM_PACKAGE = "@headways/cli";
|
|
1100
|
+
function registerUpgradeCommand(program2) {
|
|
1101
|
+
program2.command("upgrade").description("Upgrade the Headways CLI to the latest version").option("--force", "Reinstall even if already on the latest version").action(async (opts) => {
|
|
1102
|
+
warnIfShadowed();
|
|
1103
|
+
const mode = detectInstallMode();
|
|
1104
|
+
if (mode.kind === "npm") {
|
|
1105
|
+
console.log(
|
|
1106
|
+
`This binary was installed via npm. Upgrade with:
|
|
1107
|
+
|
|
1108
|
+
npm i -g ${NPM_PACKAGE}@latest
|
|
1109
|
+
`
|
|
1110
|
+
);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (mode.kind === "unknown") {
|
|
1114
|
+
console.error(
|
|
1115
|
+
`Could not determine how this CLI was installed (execPath: ${process.execPath}).
|
|
1116
|
+
If installed via npm: npm i -g ${NPM_PACKAGE}@latest
|
|
1117
|
+
If installed via desktop: re-run the Install step from the Headways app.`
|
|
1118
|
+
);
|
|
1119
|
+
process.exitCode = 1;
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
await selfUpdate(mode.binPath, opts.force ?? false);
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
function detectInstallMode() {
|
|
1126
|
+
const exec = process.execPath;
|
|
1127
|
+
const execName = basename3(exec).toLowerCase();
|
|
1128
|
+
if (execName === "node" || execName === "bun" || execName.startsWith("node")) {
|
|
1129
|
+
return { kind: "npm" };
|
|
1130
|
+
}
|
|
1131
|
+
if (execName === "headways" || execName.startsWith("headways")) {
|
|
1132
|
+
return { kind: "standalone", binPath: exec };
|
|
1133
|
+
}
|
|
1134
|
+
return { kind: "unknown" };
|
|
1135
|
+
}
|
|
1136
|
+
function detectTriple() {
|
|
1137
|
+
if (process.platform !== "darwin") return null;
|
|
1138
|
+
if (process.arch === "arm64") return "aarch64-apple-darwin";
|
|
1139
|
+
if (process.arch === "x64") return "x86_64-apple-darwin";
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
async function selfUpdate(binPath, force) {
|
|
1143
|
+
const triple = detectTriple();
|
|
1144
|
+
if (!triple) {
|
|
1145
|
+
console.error(
|
|
1146
|
+
`No prebuilt binary for ${process.platform}/${process.arch}. Standalone binaries are macOS-only; on other platforms use: npm i -g ${NPM_PACKAGE}@latest`
|
|
1147
|
+
);
|
|
1148
|
+
process.exitCode = 1;
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
console.log(`Checking for updates\u2026 (current: ${"1.1.0"})`);
|
|
1152
|
+
const latestRes = await fetch(`${GCS_BASE}/latest.txt`);
|
|
1153
|
+
if (!latestRes.ok) {
|
|
1154
|
+
console.error(`Failed to fetch latest version: ${latestRes.status} ${latestRes.statusText}`);
|
|
1155
|
+
process.exitCode = 1;
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
const latest = (await latestRes.text()).trim();
|
|
1159
|
+
if (latest === "1.1.0" && !force) {
|
|
1160
|
+
console.log(`Already on the latest version (${latest}).`);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
console.log(`Downloading ${latest} for ${triple}\u2026`);
|
|
1164
|
+
const binRes = await fetch(`${GCS_BASE}/${latest}/headways-${triple}`);
|
|
1165
|
+
if (!binRes.ok) {
|
|
1166
|
+
console.error(`Failed to download binary: ${binRes.status} ${binRes.statusText}`);
|
|
1167
|
+
process.exitCode = 1;
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const bin = Buffer.from(await binRes.arrayBuffer());
|
|
1171
|
+
const tmpPath = join6(dirname(binPath), `.headways.upgrade.${process.pid}`);
|
|
1172
|
+
writeFileSync2(tmpPath, bin);
|
|
1173
|
+
try {
|
|
1174
|
+
chmodSync(tmpPath, 493);
|
|
1175
|
+
renameSync(tmpPath, binPath);
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
try {
|
|
1178
|
+
unlinkSync2(tmpPath);
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
throw e;
|
|
1182
|
+
}
|
|
1183
|
+
console.log(`Upgraded to ${latest} \u2192 ${binPath}`);
|
|
1184
|
+
}
|
|
1185
|
+
function warnIfShadowed() {
|
|
1186
|
+
const exec = process.execPath;
|
|
1187
|
+
const pathDirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
1188
|
+
const found = [];
|
|
1189
|
+
for (const dir of pathDirs) {
|
|
1190
|
+
const candidate = join6(dir, "headways");
|
|
1191
|
+
try {
|
|
1192
|
+
if (existsSync5(candidate) && statSync2(candidate).isFile()) {
|
|
1193
|
+
found.push(candidate);
|
|
1194
|
+
}
|
|
1195
|
+
} catch {
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (found.length > 0 && found[0] !== exec) {
|
|
1199
|
+
console.error(
|
|
1200
|
+
`note: another 'headways' is earlier on PATH at ${found[0]} \u2014 running upgrade here won't change which binary your shell picks.`
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1118
1205
|
// src/index.ts
|
|
1119
|
-
program.name("headways").description("Headways CLI \u2014 skill authoring + Claude Code runtime helpers").version(
|
|
1206
|
+
program.name("headways").description("Headways CLI \u2014 skill authoring + Claude Code runtime helpers").version("1.1.0");
|
|
1120
1207
|
registerAuthCommands(program);
|
|
1121
1208
|
registerSkillsCommands(program);
|
|
1122
1209
|
registerConnectionsCommands(program);
|
|
1123
1210
|
registerEmitCommand(program);
|
|
1124
1211
|
registerSkillRunCommands(program);
|
|
1125
1212
|
registerPrimeCommand(program);
|
|
1213
|
+
registerUpgradeCommand(program);
|
|
1126
1214
|
program.parse();
|