opencodekit 0.18.11 → 0.18.13

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