opencodekit 0.18.12 → 0.18.14
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
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
3
4
|
import { cac } from "cac";
|
|
5
|
+
import color from "picocolors";
|
|
6
|
+
import { createHash, createHmac } from "node:crypto";
|
|
4
7
|
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir, hostname, platform, release } from "node:os";
|
|
5
9
|
import { basename, dirname, join, relative } from "node:path";
|
|
6
|
-
import
|
|
7
|
-
import
|
|
10
|
+
import envPaths from "env-paths";
|
|
11
|
+
import machineId from "node-machine-id";
|
|
8
12
|
import { z } from "zod";
|
|
9
13
|
import { execSync, spawn } from "node:child_process";
|
|
10
|
-
import { homedir, platform } from "node:os";
|
|
11
14
|
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { createHash } from "node:crypto";
|
|
13
15
|
import { applyPatch, createPatch } from "diff";
|
|
14
16
|
import * as readline from "node:readline";
|
|
15
17
|
|
|
@@ -18,7 +20,246 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
18
20
|
|
|
19
21
|
//#endregion
|
|
20
22
|
//#region package.json
|
|
21
|
-
var version = "0.18.
|
|
23
|
+
var version = "0.18.14";
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/utils/license.ts
|
|
27
|
+
const { machineIdSync } = machineId;
|
|
28
|
+
const LICENSE_API_BASE_URL = process.env.OCK_LICENSE_API_BASE_URL ?? "https://payments-api.opencodekit.xyz";
|
|
29
|
+
const LICENSE_REQUEST_TIMEOUT_MS = 1e4;
|
|
30
|
+
const LICENSE_HMAC_SECRET = process.env.OCK_LICENSE_HMAC_SECRET ?? "ock-license-v1-local-integrity";
|
|
31
|
+
const LICENSE_CHECK_INTERVAL_SECONDS = 10080 * 60;
|
|
32
|
+
const LICENSE_OFFLINE_GRACE_SECONDS = 720 * 60 * 60;
|
|
33
|
+
const LICENSE_KEY_PATTERN = /^OCK-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
|
|
34
|
+
const StoredLicenseSchema = z.object({
|
|
35
|
+
key: z.string(),
|
|
36
|
+
activationId: z.string(),
|
|
37
|
+
machineId: z.string(),
|
|
38
|
+
machineLabel: z.string(),
|
|
39
|
+
activatedAt: z.number(),
|
|
40
|
+
lastValidatedAt: z.number(),
|
|
41
|
+
nextCheckAt: z.number(),
|
|
42
|
+
hmac: z.string()
|
|
43
|
+
});
|
|
44
|
+
function unixNow() {
|
|
45
|
+
return Math.floor(Date.now() / 1e3);
|
|
46
|
+
}
|
|
47
|
+
function getLicensePaths() {
|
|
48
|
+
const paths = envPaths("opencodekit", { suffix: "" });
|
|
49
|
+
return {
|
|
50
|
+
dir: join(paths.data, "license"),
|
|
51
|
+
file: join(paths.data, "license", "license.json")
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function toSign(payload) {
|
|
55
|
+
return [
|
|
56
|
+
payload.key,
|
|
57
|
+
payload.activationId,
|
|
58
|
+
payload.machineId,
|
|
59
|
+
payload.machineLabel,
|
|
60
|
+
payload.activatedAt,
|
|
61
|
+
payload.lastValidatedAt,
|
|
62
|
+
payload.nextCheckAt
|
|
63
|
+
].join("|");
|
|
64
|
+
}
|
|
65
|
+
function computeHmac(payload) {
|
|
66
|
+
return createHmac("sha256", LICENSE_HMAC_SECRET).update(toSign(payload), "utf8").digest("hex");
|
|
67
|
+
}
|
|
68
|
+
function getMachineId() {
|
|
69
|
+
return machineIdSync(false);
|
|
70
|
+
}
|
|
71
|
+
function getMachineLabel() {
|
|
72
|
+
return `${hostname()} (${platform()} ${release()})`;
|
|
73
|
+
}
|
|
74
|
+
function getLicenseFilePath() {
|
|
75
|
+
return getLicensePaths().file;
|
|
76
|
+
}
|
|
77
|
+
function readStoredLicense() {
|
|
78
|
+
const { file } = getLicensePaths();
|
|
79
|
+
if (!existsSync(file)) return null;
|
|
80
|
+
try {
|
|
81
|
+
const raw = readFileSync(file, "utf8");
|
|
82
|
+
const parsed = StoredLicenseSchema.safeParse(JSON.parse(raw));
|
|
83
|
+
if (!parsed.success) return null;
|
|
84
|
+
return parsed.data;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function deleteStoredLicense() {
|
|
90
|
+
const { file } = getLicensePaths();
|
|
91
|
+
if (!existsSync(file)) return;
|
|
92
|
+
rmSync(file, { force: true });
|
|
93
|
+
}
|
|
94
|
+
function isLicenseBypassEnabled() {
|
|
95
|
+
return process.env.OCK_LICENSE_BYPASS === "1";
|
|
96
|
+
}
|
|
97
|
+
function isLicenseKeyFormatValid(key) {
|
|
98
|
+
return LICENSE_KEY_PATTERN.test(key.trim().toUpperCase());
|
|
99
|
+
}
|
|
100
|
+
function isStoredLicenseIntegrityValid(stored) {
|
|
101
|
+
return computeHmac({
|
|
102
|
+
key: stored.key,
|
|
103
|
+
activationId: stored.activationId,
|
|
104
|
+
machineId: stored.machineId,
|
|
105
|
+
machineLabel: stored.machineLabel,
|
|
106
|
+
activatedAt: stored.activatedAt,
|
|
107
|
+
lastValidatedAt: stored.lastValidatedAt,
|
|
108
|
+
nextCheckAt: stored.nextCheckAt
|
|
109
|
+
}) === stored.hmac;
|
|
110
|
+
}
|
|
111
|
+
function writeStoredLicense(payload) {
|
|
112
|
+
const { dir, file } = getLicensePaths();
|
|
113
|
+
mkdirSync(dir, { recursive: true });
|
|
114
|
+
const stored = {
|
|
115
|
+
...payload,
|
|
116
|
+
hmac: computeHmac(payload)
|
|
117
|
+
};
|
|
118
|
+
writeFileSync(file, `${JSON.stringify(stored, null, 2)}\n`, "utf8");
|
|
119
|
+
return stored;
|
|
120
|
+
}
|
|
121
|
+
async function postJson(path, body) {
|
|
122
|
+
let response;
|
|
123
|
+
try {
|
|
124
|
+
response = await fetch(`${LICENSE_API_BASE_URL}${path}`, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: { "content-type": "application/json" },
|
|
127
|
+
body: JSON.stringify(body),
|
|
128
|
+
signal: AbortSignal.timeout(LICENSE_REQUEST_TIMEOUT_MS)
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
+
throw new Error(`License server request failed: ${message}`);
|
|
133
|
+
}
|
|
134
|
+
let data;
|
|
135
|
+
try {
|
|
136
|
+
data = await response.json();
|
|
137
|
+
} catch {
|
|
138
|
+
throw new Error(`License server returned invalid JSON (${response.status})`);
|
|
139
|
+
}
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
const maybe = data;
|
|
142
|
+
throw new Error(maybe?.message || `License server request failed with status ${response.status}`);
|
|
143
|
+
}
|
|
144
|
+
return data;
|
|
145
|
+
}
|
|
146
|
+
async function activateLicenseKey(key) {
|
|
147
|
+
const normalizedKey = key.trim().toUpperCase();
|
|
148
|
+
const machineId = getMachineId();
|
|
149
|
+
const machineLabel = getMachineLabel();
|
|
150
|
+
const now = unixNow();
|
|
151
|
+
const data = await postJson("/v1/activate", {
|
|
152
|
+
key: normalizedKey,
|
|
153
|
+
machine_id: machineId,
|
|
154
|
+
machine_label: machineLabel
|
|
155
|
+
});
|
|
156
|
+
if (!data.ok || !data.activation_id) throw new Error(data.message || data.error || "License activation failed");
|
|
157
|
+
return writeStoredLicense({
|
|
158
|
+
key: normalizedKey,
|
|
159
|
+
activationId: data.activation_id,
|
|
160
|
+
machineId,
|
|
161
|
+
machineLabel,
|
|
162
|
+
activatedAt: now,
|
|
163
|
+
lastValidatedAt: now,
|
|
164
|
+
nextCheckAt: now + LICENSE_CHECK_INTERVAL_SECONDS
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function validateLicenseOnline(stored) {
|
|
168
|
+
if (getMachineId() !== stored.machineId) throw new Error("License key is activated on a different machine");
|
|
169
|
+
const data = await postJson("/v1/validate", {
|
|
170
|
+
key: stored.key,
|
|
171
|
+
activation_id: stored.activationId,
|
|
172
|
+
machine_id: stored.machineId
|
|
173
|
+
});
|
|
174
|
+
if (!data.ok) throw new Error(data.message || data.error || "License validation failed");
|
|
175
|
+
const now = unixNow();
|
|
176
|
+
writeStoredLicense({
|
|
177
|
+
key: stored.key,
|
|
178
|
+
activationId: stored.activationId,
|
|
179
|
+
machineId: stored.machineId,
|
|
180
|
+
machineLabel: stored.machineLabel,
|
|
181
|
+
activatedAt: stored.activatedAt,
|
|
182
|
+
lastValidatedAt: now,
|
|
183
|
+
nextCheckAt: now + LICENSE_CHECK_INTERVAL_SECONDS
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async function deactivateCurrentLicense(stored) {
|
|
187
|
+
const data = await postJson("/v1/deactivate", {
|
|
188
|
+
key: stored.key,
|
|
189
|
+
activation_id: stored.activationId,
|
|
190
|
+
machine_id: stored.machineId
|
|
191
|
+
});
|
|
192
|
+
if (!data.ok) throw new Error(data.message || data.error || "License deactivation failed");
|
|
193
|
+
deleteStoredLicense();
|
|
194
|
+
}
|
|
195
|
+
async function requireValidLicense() {
|
|
196
|
+
if (isLicenseBypassEnabled()) return {
|
|
197
|
+
mode: "bypass",
|
|
198
|
+
stored: null
|
|
199
|
+
};
|
|
200
|
+
const stored = readStoredLicense();
|
|
201
|
+
if (!stored) throw new Error("License not activated. Run: ock activate <LICENSE_KEY>");
|
|
202
|
+
if (!isStoredLicenseIntegrityValid(stored)) throw new Error(`License file integrity check failed (${dirname(getLicenseFilePath())}). Re-activate with: ock activate <LICENSE_KEY>`);
|
|
203
|
+
if (stored.machineId !== getMachineId()) throw new Error("License is tied to a different machine. Run: ock activate <LICENSE_KEY>");
|
|
204
|
+
const now = unixNow();
|
|
205
|
+
if (stored.nextCheckAt > now) return {
|
|
206
|
+
mode: "active",
|
|
207
|
+
stored
|
|
208
|
+
};
|
|
209
|
+
try {
|
|
210
|
+
await validateLicenseOnline(stored);
|
|
211
|
+
return {
|
|
212
|
+
mode: "active",
|
|
213
|
+
stored: readStoredLicense()
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (now - stored.lastValidatedAt <= LICENSE_OFFLINE_GRACE_SECONDS) return {
|
|
217
|
+
mode: "offline-grace",
|
|
218
|
+
stored
|
|
219
|
+
};
|
|
220
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
221
|
+
throw new Error(`License validation failed: ${message}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/commands/activate.ts
|
|
227
|
+
async function activateCommand(keyArg) {
|
|
228
|
+
p.intro(color.bgBlue(color.black(" License Activation ")));
|
|
229
|
+
let key = keyArg?.trim();
|
|
230
|
+
if (!key) {
|
|
231
|
+
const answer = await p.text({
|
|
232
|
+
message: "Enter your license key",
|
|
233
|
+
placeholder: "OCK-XXXX-XXXX-XXXX",
|
|
234
|
+
validate: (value) => {
|
|
235
|
+
if (!value) return "License key is required";
|
|
236
|
+
if (!isLicenseKeyFormatValid(value)) return "Invalid format. Expected OCK-XXXX-XXXX-XXXX";
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
if (p.isCancel(answer)) {
|
|
240
|
+
p.cancel("Cancelled");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
key = String(answer);
|
|
244
|
+
}
|
|
245
|
+
if (!isLicenseKeyFormatValid(key)) {
|
|
246
|
+
p.log.error("Invalid license key format. Expected OCK-XXXX-XXXX-XXXX");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const spinner = p.spinner();
|
|
250
|
+
spinner.start("Activating license");
|
|
251
|
+
try {
|
|
252
|
+
const stored = await activateLicenseKey(key);
|
|
253
|
+
spinner.stop("License activated");
|
|
254
|
+
p.log.success(`Activated ${color.cyan(stored.key)}`);
|
|
255
|
+
p.log.info(`Stored at ${color.dim(getLicenseFilePath())}`);
|
|
256
|
+
p.outro(color.green("Activation complete"));
|
|
257
|
+
} catch (error) {
|
|
258
|
+
spinner.stop("Activation failed");
|
|
259
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
260
|
+
p.log.error(message);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
22
263
|
|
|
23
264
|
//#endregion
|
|
24
265
|
//#region src/utils/errors.ts
|
|
@@ -3353,6 +3594,80 @@ async function initCommand(rawOptions = {}) {
|
|
|
3353
3594
|
p.outro(color.green("Ready to code!"));
|
|
3354
3595
|
}
|
|
3355
3596
|
|
|
3597
|
+
//#endregion
|
|
3598
|
+
//#region src/commands/license.ts
|
|
3599
|
+
async function licenseCommand(action) {
|
|
3600
|
+
const mode = (action ?? "status").trim();
|
|
3601
|
+
if (mode === "status") {
|
|
3602
|
+
await showLicenseStatus();
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
if (mode === "deactivate") {
|
|
3606
|
+
await deactivateLicense();
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
p.log.error(`Unknown action: ${mode}`);
|
|
3610
|
+
p.log.info(`Available: ${color.cyan("status")}, ${color.cyan("deactivate")}`);
|
|
3611
|
+
}
|
|
3612
|
+
async function showLicenseStatus() {
|
|
3613
|
+
const stored = readStoredLicense();
|
|
3614
|
+
if (!stored) {
|
|
3615
|
+
p.log.warn("No license is currently activated");
|
|
3616
|
+
p.log.info(`Run ${color.cyan("ock activate <LICENSE_KEY>")}`);
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
p.log.info(color.bold("License Status"));
|
|
3620
|
+
console.log(` key: ${color.cyan(stored.key)}`);
|
|
3621
|
+
console.log(` activation: ${stored.activationId}`);
|
|
3622
|
+
console.log(` machine: ${stored.machineLabel}`);
|
|
3623
|
+
console.log(` last check: ${(/* @__PURE__ */ new Date(stored.lastValidatedAt * 1e3)).toISOString()}`);
|
|
3624
|
+
console.log(` next check: ${(/* @__PURE__ */ new Date(stored.nextCheckAt * 1e3)).toISOString()}`);
|
|
3625
|
+
console.log(` file: ${color.dim(getLicenseFilePath())}`);
|
|
3626
|
+
const spinner = p.spinner();
|
|
3627
|
+
spinner.start("Verifying with license server");
|
|
3628
|
+
try {
|
|
3629
|
+
await validateLicenseOnline(stored);
|
|
3630
|
+
spinner.stop("License is valid");
|
|
3631
|
+
} catch (error) {
|
|
3632
|
+
spinner.stop("Validation failed");
|
|
3633
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3634
|
+
p.log.warn(message);
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
async function deactivateLicense() {
|
|
3638
|
+
const stored = readStoredLicense();
|
|
3639
|
+
if (!stored) {
|
|
3640
|
+
p.log.warn("No license is currently activated");
|
|
3641
|
+
return;
|
|
3642
|
+
}
|
|
3643
|
+
const confirm = await p.confirm({
|
|
3644
|
+
message: `Deactivate license ${stored.key} on this machine?`,
|
|
3645
|
+
initialValue: false
|
|
3646
|
+
});
|
|
3647
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
3648
|
+
p.cancel("Cancelled");
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
const spinner = p.spinner();
|
|
3652
|
+
spinner.start("Deactivating license");
|
|
3653
|
+
try {
|
|
3654
|
+
await deactivateCurrentLicense(stored);
|
|
3655
|
+
spinner.stop("License deactivated");
|
|
3656
|
+
p.outro(color.green("This machine no longer uses the license"));
|
|
3657
|
+
} catch (error) {
|
|
3658
|
+
spinner.stop("Deactivation failed");
|
|
3659
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3660
|
+
p.log.warn(`Remote deactivation failed: ${message}`);
|
|
3661
|
+
const retry = await p.confirm({
|
|
3662
|
+
message: "Remove local license file anyway?",
|
|
3663
|
+
initialValue: false
|
|
3664
|
+
});
|
|
3665
|
+
if (p.isCancel(retry) || !retry) return;
|
|
3666
|
+
deleteStoredLicense();
|
|
3667
|
+
p.log.info(`Removed local license at ${color.dim(getLicenseFilePath())}`);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3356
3671
|
//#endregion
|
|
3357
3672
|
//#region src/commands/skill.ts
|
|
3358
3673
|
async function skillCommand(action) {
|
|
@@ -5612,10 +5927,32 @@ if (process.stdout.setEncoding) process.stdout.setEncoding("utf8");
|
|
|
5612
5927
|
if (process.stderr.setEncoding) process.stderr.setEncoding("utf8");
|
|
5613
5928
|
const packageVersion = version;
|
|
5614
5929
|
const cli = cac("ock");
|
|
5930
|
+
async function ensureLicenseFor(commandName) {
|
|
5931
|
+
try {
|
|
5932
|
+
if ((await requireValidLicense()).mode === "offline-grace") p.log.warn("License server is unreachable. Running in offline grace mode.");
|
|
5933
|
+
return true;
|
|
5934
|
+
} catch (error) {
|
|
5935
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5936
|
+
p.log.error(`${commandName} requires a valid license`);
|
|
5937
|
+
p.log.info(message);
|
|
5938
|
+
p.log.info("Run: ock activate <LICENSE_KEY>");
|
|
5939
|
+
process.exitCode = 1;
|
|
5940
|
+
return false;
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5615
5943
|
cli.option("--verbose", "Enable verbose logging");
|
|
5616
5944
|
cli.option("--quiet", "Suppress all output");
|
|
5617
5945
|
cli.version(`${packageVersion}`);
|
|
5618
|
-
cli.command("init", "Initialize OpenCodeKit in current directory").option("--force", "Reinitialize even if already exists").option("--beads", "Also initialize .beads/ for multi-agent coordination").option("--global", "Install to global OpenCode config (~/.config/opencode/)").option("--free", "Use free models (default)").option("--recommend", "Use recommended premium models").option("-y, --yes", "Skip prompts, use defaults (for CI)").option("--backup", "Backup existing .opencode before overwriting").option("--prune", "Manually select orphan files to delete").option("--prune-all", "Auto-delete all orphan files").option("--project-only", "Only init project-scope files (skip if global config has agents/skills/commands/tools)").action(
|
|
5946
|
+
cli.command("init", "Initialize OpenCodeKit in current directory").option("--force", "Reinitialize even if already exists").option("--beads", "Also initialize .beads/ for multi-agent coordination").option("--global", "Install to global OpenCode config (~/.config/opencode/)").option("--free", "Use free models (default)").option("--recommend", "Use recommended premium models").option("-y, --yes", "Skip prompts, use defaults (for CI)").option("--backup", "Backup existing .opencode before overwriting").option("--prune", "Manually select orphan files to delete").option("--prune-all", "Auto-delete all orphan files").option("--project-only", "Only init project-scope files (skip if global config has agents/skills/commands/tools)").action(async (options) => {
|
|
5947
|
+
if (!await ensureLicenseFor("init")) return;
|
|
5948
|
+
await initCommand(options);
|
|
5949
|
+
});
|
|
5950
|
+
cli.command("activate [key]", "Activate paid license key").action(async (key) => {
|
|
5951
|
+
await activateCommand(key);
|
|
5952
|
+
});
|
|
5953
|
+
cli.command("license [action]", "Manage license (status, deactivate)").action(async (action) => {
|
|
5954
|
+
await licenseCommand(action);
|
|
5955
|
+
});
|
|
5619
5956
|
cli.command("agent [action]", "Manage agents (list, add, view)").action(async (action) => {
|
|
5620
5957
|
if (!action) {
|
|
5621
5958
|
console.log("\nUsage: ock agent <action>\n");
|
|
@@ -5657,6 +5994,7 @@ cli.command("config [action]", "Edit opencode.json (model, mcp, permission, vali
|
|
|
5657
5994
|
await configCommand(action);
|
|
5658
5995
|
});
|
|
5659
5996
|
cli.command("upgrade", "Update .opencode/ templates to latest version").option("--force", "Force upgrade even if already up to date").option("--check", "Check for updates without upgrading").option("--prune", "Manually select orphan files to delete").option("--prune-all", "Auto-delete all orphan files").action(async (options) => {
|
|
5997
|
+
if (!await ensureLicenseFor("upgrade")) return;
|
|
5660
5998
|
await upgradeCommand(options);
|
|
5661
5999
|
});
|
|
5662
6000
|
cli.command("patch [action]", "Manage template patches (list, create, apply, diff, remove, disable, enable)").action(async (action) => {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|