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 * as p from "@clack/prompts";
7
- import color from "picocolors";
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.12";
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(initCommand);
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