zk-credits 0.1.0 → 0.1.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.
@@ -10,6 +10,7 @@ export interface CliCommandDependencies {
10
10
  isCodexProfileInstalled(): Promise<boolean>;
11
11
  isSidecarHealthy(): Promise<boolean>;
12
12
  launchCodex(args: readonly string[]): Promise<number>;
13
+ launchCline(args: readonly string[], localToken: string): Promise<number>;
13
14
  }
14
15
  /** Handles the non-server CLI commands independently from terminal I/O. */
15
16
  export declare function runCliCommand(args: readonly string[], dependencies: CliCommandDependencies): Promise<number>;
@@ -49,7 +49,15 @@ export async function runCliCommand(args, dependencies) {
49
49
  await dependencies.ensureSidecar();
50
50
  return dependencies.launchCodex(args.slice(1));
51
51
  }
52
+ case 'cline': {
53
+ if (!await dependencies.isIdentityConfigured()) {
54
+ await dependencies.importMnemonic(await dependencies.readMnemonic());
55
+ dependencies.write('ZK Credits identity imported into the system credential store.');
56
+ }
57
+ const localToken = await dependencies.ensureSidecar();
58
+ return dependencies.launchCline(args.slice(1), localToken);
59
+ }
52
60
  default:
53
- throw new Error('Usage: zk-credits <setup codex|codex|status|token|import-mnemonic|serve|env>');
61
+ throw new Error('Usage: zk-credits <cline|setup codex|codex|status|token|import-mnemonic|serve|env>');
54
62
  }
55
63
  }
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { launchCodexProcess } from './codex-launcher.js';
5
+ import { launchClineProcess } from './cline-launcher.js';
5
6
  import { isCodexProfileInstalled, resolveCodexHome, writeCodexProfile, } from './codex-profile.js';
6
7
  import { IdentityStore } from './identity.js';
7
8
  import { createLocalProofGenerator } from './local-prover.js';
@@ -77,6 +78,7 @@ async function readHiddenMnemonic() {
77
78
  }
78
79
  function printHelp() {
79
80
  console.log(`Usage:
81
+ zk-credits cline [cline arguments...]
80
82
  zk-credits setup codex [--model <model>]
81
83
  zk-credits codex [codex arguments...]
82
84
  zk-credits status
@@ -84,10 +86,10 @@ function printHelp() {
84
86
  zk-credits serve [--port <port>]
85
87
  eval "$(zk-credits env)"
86
88
 
87
- The Codex setup starts the loopback sidecar automatically; daily use is
88
- "zk-credits codex". serve/env remain available for other OpenAI-compatible
89
- clients. Set ZK_CREDITS_MNEMONIC only for a headless process; it is not
90
- persisted by that path.`);
89
+ "zk-credits cline" is the zero-configuration coding-agent path: it starts the
90
+ sidecar, configures an isolated OpenAI-compatible Cline profile, and forwards
91
+ all Cline arguments. The Codex companion remains available through setup codex.
92
+ Set ZK_CREDITS_MNEMONIC only for a headless process; it is not persisted.`);
91
93
  }
92
94
  async function serve(args) {
93
95
  const port = readPort(args);
@@ -165,6 +167,12 @@ async function main() {
165
167
  isCodexProfileInstalled: () => isCodexProfileInstalled(codexHome),
166
168
  isSidecarHealthy: () => lifecycle.isHealthy(),
167
169
  launchCodex: (codexArgs) => launchCodexProcess(codexArgs),
170
+ launchCline: (clineArgs, localToken) => launchClineProcess({
171
+ args: clineArgs,
172
+ loopbackBaseUrl: loopbackBaseUrl(),
173
+ localToken,
174
+ stateDirectory: sidecarHome,
175
+ }),
168
176
  });
169
177
  if (exitCode !== 0)
170
178
  process.exitCode = exitCode;
@@ -0,0 +1,18 @@
1
+ interface SpawnedProcess {
2
+ once(event: 'error', listener: (error: NodeJS.ErrnoException) => void): this;
3
+ once(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
4
+ }
5
+ type ClineStdio = 'inherit' | ['ignore', 'ignore', 'inherit'];
6
+ export type ClineSpawnCommand = (command: string, args: readonly string[], options: {
7
+ stdio: ClineStdio;
8
+ env: NodeJS.ProcessEnv;
9
+ }) => SpawnedProcess;
10
+ export interface LaunchClineOptions {
11
+ args: readonly string[];
12
+ loopbackBaseUrl: string;
13
+ localToken: string;
14
+ stateDirectory: string;
15
+ }
16
+ /** Configures and launches Cline against the proof-aware loopback transport. */
17
+ export declare function launchClineProcess(options: LaunchClineOptions, spawnImpl?: ClineSpawnCommand): Promise<number>;
18
+ export {};
@@ -0,0 +1,85 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { chmod, mkdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ const DEFAULT_CLINE_MODEL = 'openai/gpt-4o-mini';
5
+ const spawnCommand = (command, args, options) => spawn(command, [...args], options);
6
+ function openAiBaseUrl(loopbackBaseUrl) {
7
+ const url = new URL(loopbackBaseUrl);
8
+ if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1') {
9
+ throw new Error('Cline companion must use a 127.0.0.1 HTTP sidecar URL');
10
+ }
11
+ return `${url.origin}/v1`;
12
+ }
13
+ function selectedModel(args) {
14
+ for (let index = 0; index < args.length; index += 1) {
15
+ const argument = args[index];
16
+ if (argument === '--model' || argument === '-m') {
17
+ const model = args[index + 1]?.trim();
18
+ if (!model)
19
+ throw new Error(`${argument} requires a model ID`);
20
+ return model;
21
+ }
22
+ if (argument?.startsWith('--model=')) {
23
+ const model = argument.slice('--model='.length).trim();
24
+ if (!model)
25
+ throw new Error('--model requires a model ID');
26
+ return model;
27
+ }
28
+ }
29
+ return DEFAULT_CLINE_MODEL;
30
+ }
31
+ function assertManagedProviderArguments(args) {
32
+ const managed = new Set(['--provider', '-P', '--key', '-k', '--config', '--data-dir']);
33
+ if (args.some((argument) => managed.has(argument) || argument.startsWith('--provider='))) {
34
+ throw new Error('zk-credits cline manages the provider, API key, and profile directory');
35
+ }
36
+ }
37
+ function waitForCline(child) {
38
+ return new Promise((resolve, reject) => {
39
+ child.once('error', (error) => {
40
+ if (error.code === 'ENOENT') {
41
+ reject(new Error('Cline CLI was not found; install it with npm install --global cline'));
42
+ return;
43
+ }
44
+ reject(error);
45
+ });
46
+ child.once('exit', (code) => resolve(code ?? 1));
47
+ });
48
+ }
49
+ /** Configures and launches Cline against the proof-aware loopback transport. */
50
+ export async function launchClineProcess(options, spawnImpl = spawnCommand) {
51
+ assertManagedProviderArguments(options.args);
52
+ const baseUrl = openAiBaseUrl(options.loopbackBaseUrl);
53
+ const model = selectedModel(options.args);
54
+ const dataDirectory = join(options.stateDirectory, 'cline');
55
+ await mkdir(dataDirectory, { recursive: true, mode: 0o700 });
56
+ await chmod(dataDirectory, 0o700);
57
+ const environment = {
58
+ ...process.env,
59
+ OPENAI_BASE_URL: baseUrl,
60
+ OPENAI_API_KEY: options.localToken,
61
+ };
62
+ const authCode = await waitForCline(spawnImpl('cline', [
63
+ 'auth',
64
+ '--provider', 'openai-compatible',
65
+ '--apikey', options.localToken,
66
+ '--modelid', model,
67
+ '--baseurl', baseUrl,
68
+ '--data-dir', dataDirectory,
69
+ ], {
70
+ stdio: ['ignore', 'ignore', 'inherit'],
71
+ env: environment,
72
+ }));
73
+ if (authCode !== 0) {
74
+ throw new Error(`Cline OpenAI-compatible provider setup failed with exit code ${authCode}`);
75
+ }
76
+ return waitForCline(spawnImpl('cline', [
77
+ '--provider', 'openai-compatible',
78
+ '--model', model,
79
+ '--data-dir', dataDirectory,
80
+ ...options.args,
81
+ ], {
82
+ stdio: 'inherit',
83
+ env: environment,
84
+ }));
85
+ }
package/dist/sidecar.js CHANGED
@@ -104,7 +104,7 @@ export function createSidecarServer(options) {
104
104
  body: body.raw,
105
105
  });
106
106
  await relayGatewayResponse(res, upstream);
107
- if (upstream.status >= 200 && upstream.status < 300) {
107
+ if (upstream.status >= 200 && upstream.status < 300 && upstream.status !== 202) {
108
108
  await options.ledger.consume(reservation.requestDigest);
109
109
  }
110
110
  }
@@ -3,7 +3,7 @@ import { createRequire as __createNodeRequire } from 'node:module'; const requir
3
3
 
4
4
  // dist/cli.js
5
5
  import { homedir } from "node:os";
6
- import { join as join4 } from "node:path";
6
+ import { join as join5 } from "node:path";
7
7
 
8
8
  // dist/codex-launcher.js
9
9
  import { spawn } from "node:child_process";
@@ -127,6 +127,100 @@ async function launchCodexProcess(args, spawnImpl = spawnCommand) {
127
127
  });
128
128
  }
129
129
 
130
+ // dist/cline-launcher.js
131
+ import { spawn as spawn2 } from "node:child_process";
132
+ import { chmod as chmod2, mkdir as mkdir2 } from "node:fs/promises";
133
+ import { join as join2 } from "node:path";
134
+ var DEFAULT_CLINE_MODEL = "openai/gpt-4o-mini";
135
+ var spawnCommand2 = (command, args, options) => spawn2(command, [...args], options);
136
+ function openAiBaseUrl(loopbackBaseUrl2) {
137
+ const url = new URL(loopbackBaseUrl2);
138
+ if (url.protocol !== "http:" || url.hostname !== "127.0.0.1") {
139
+ throw new Error("Cline companion must use a 127.0.0.1 HTTP sidecar URL");
140
+ }
141
+ return `${url.origin}/v1`;
142
+ }
143
+ function selectedModel(args) {
144
+ for (let index = 0; index < args.length; index += 1) {
145
+ const argument = args[index];
146
+ if (argument === "--model" || argument === "-m") {
147
+ const model = args[index + 1]?.trim();
148
+ if (!model)
149
+ throw new Error(`${argument} requires a model ID`);
150
+ return model;
151
+ }
152
+ if (argument?.startsWith("--model=")) {
153
+ const model = argument.slice("--model=".length).trim();
154
+ if (!model)
155
+ throw new Error("--model requires a model ID");
156
+ return model;
157
+ }
158
+ }
159
+ return DEFAULT_CLINE_MODEL;
160
+ }
161
+ function assertManagedProviderArguments(args) {
162
+ const managed = /* @__PURE__ */ new Set(["--provider", "-P", "--key", "-k", "--config", "--data-dir"]);
163
+ if (args.some((argument) => managed.has(argument) || argument.startsWith("--provider="))) {
164
+ throw new Error("zk-credits cline manages the provider, API key, and profile directory");
165
+ }
166
+ }
167
+ function waitForCline(child) {
168
+ return new Promise((resolve2, reject) => {
169
+ child.once("error", (error) => {
170
+ if (error.code === "ENOENT") {
171
+ reject(new Error("Cline CLI was not found; install it with npm install --global cline"));
172
+ return;
173
+ }
174
+ reject(error);
175
+ });
176
+ child.once("exit", (code) => resolve2(code ?? 1));
177
+ });
178
+ }
179
+ async function launchClineProcess(options, spawnImpl = spawnCommand2) {
180
+ assertManagedProviderArguments(options.args);
181
+ const baseUrl = openAiBaseUrl(options.loopbackBaseUrl);
182
+ const model = selectedModel(options.args);
183
+ const dataDirectory = join2(options.stateDirectory, "cline");
184
+ await mkdir2(dataDirectory, { recursive: true, mode: 448 });
185
+ await chmod2(dataDirectory, 448);
186
+ const environment = {
187
+ ...process.env,
188
+ OPENAI_BASE_URL: baseUrl,
189
+ OPENAI_API_KEY: options.localToken
190
+ };
191
+ const authCode = await waitForCline(spawnImpl("cline", [
192
+ "auth",
193
+ "--provider",
194
+ "openai-compatible",
195
+ "--apikey",
196
+ options.localToken,
197
+ "--modelid",
198
+ model,
199
+ "--baseurl",
200
+ baseUrl,
201
+ "--data-dir",
202
+ dataDirectory
203
+ ], {
204
+ stdio: ["ignore", "ignore", "inherit"],
205
+ env: environment
206
+ }));
207
+ if (authCode !== 0) {
208
+ throw new Error(`Cline OpenAI-compatible provider setup failed with exit code ${authCode}`);
209
+ }
210
+ return waitForCline(spawnImpl("cline", [
211
+ "--provider",
212
+ "openai-compatible",
213
+ "--model",
214
+ model,
215
+ "--data-dir",
216
+ dataDirectory,
217
+ ...options.args
218
+ ], {
219
+ stdio: "inherit",
220
+ env: environment
221
+ }));
222
+ }
223
+
130
224
  // dist/identity.js
131
225
  import * as keytar from "keytar";
132
226
 
@@ -398,7 +492,7 @@ import { resolve } from "node:path";
398
492
  // dist/artifact-manifest.js
399
493
  import { createHash } from "node:crypto";
400
494
  import { readFile } from "node:fs/promises";
401
- import { join as join2 } from "node:path";
495
+ import { join as join3 } from "node:path";
402
496
  var CIRCUIT_MANIFEST = [
403
497
  {
404
498
  file: "rln_nullifier.wasm",
@@ -415,7 +509,7 @@ var CIRCUIT_MANIFEST = [
415
509
  ];
416
510
  async function verifyArtifactManifest(artifactDirectory, manifest = CIRCUIT_MANIFEST) {
417
511
  for (const artifact of manifest) {
418
- const content = await readFile(join2(artifactDirectory, artifact.file));
512
+ const content = await readFile(join3(artifactDirectory, artifact.file));
419
513
  const digest = createHash("sha256").update(content).digest("hex");
420
514
  if (digest !== artifact.sha256) {
421
515
  throw new Error(`Circuit artifact hash mismatch: ${artifact.file}`);
@@ -509,15 +603,15 @@ var MembershipClient = class {
509
603
 
510
604
  // dist/sidecar-config.js
511
605
  import { randomBytes as randomBytes2 } from "node:crypto";
512
- import { join as join3 } from "node:path";
606
+ import { join as join4 } from "node:path";
513
607
  function createLoopbackToken() {
514
608
  return randomBytes2(32).toString("base64url");
515
609
  }
516
610
  function sidecarStatePaths(stateDirectory2) {
517
611
  return {
518
- ledgerPath: join3(stateDirectory2, "tickets.json"),
519
- tokenPath: join3(stateDirectory2, "loopback-token"),
520
- logPath: join3(stateDirectory2, "sidecar.log")
612
+ ledgerPath: join4(stateDirectory2, "tickets.json"),
613
+ tokenPath: join4(stateDirectory2, "loopback-token"),
614
+ logPath: join4(stateDirectory2, "sidecar.log")
521
615
  };
522
616
  }
523
617
  function formatOpenAiEnvironment(loopbackBaseUrl2, localToken) {
@@ -581,29 +675,37 @@ async function runCliCommand(args, dependencies) {
581
675
  await dependencies.ensureSidecar();
582
676
  return dependencies.launchCodex(args.slice(1));
583
677
  }
678
+ case "cline": {
679
+ if (!await dependencies.isIdentityConfigured()) {
680
+ await dependencies.importMnemonic(await dependencies.readMnemonic());
681
+ dependencies.write("ZK Credits identity imported into the system credential store.");
682
+ }
683
+ const localToken = await dependencies.ensureSidecar();
684
+ return dependencies.launchCline(args.slice(1), localToken);
685
+ }
584
686
  default:
585
- throw new Error("Usage: zk-credits <setup codex|codex|status|token|import-mnemonic|serve|env>");
687
+ throw new Error("Usage: zk-credits <cline|setup codex|codex|status|token|import-mnemonic|serve|env>");
586
688
  }
587
689
  }
588
690
 
589
691
  // dist/node-sidecar-lifecycle.js
590
- import { spawn as spawn2 } from "node:child_process";
591
- import { chmod as chmod3, mkdir as mkdir3, open } from "node:fs/promises";
692
+ import { spawn as spawn3 } from "node:child_process";
693
+ import { chmod as chmod4, mkdir as mkdir4, open } from "node:fs/promises";
592
694
  import { dirname as dirname2 } from "node:path";
593
695
 
594
696
  // dist/sidecar-state.js
595
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
697
+ import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
596
698
  import { dirname } from "node:path";
597
699
  async function writeLoopbackToken(tokenPath, token) {
598
700
  if (!token)
599
701
  throw new Error("Loopback token is required");
600
- await mkdir2(dirname(tokenPath), { recursive: true, mode: 448 });
702
+ await mkdir3(dirname(tokenPath), { recursive: true, mode: 448 });
601
703
  const temporaryPath = `${tokenPath}.tmp`;
602
704
  await writeFile2(temporaryPath, `${token}
603
705
  `, { encoding: "utf8", mode: 384 });
604
- await chmod2(temporaryPath, 384);
706
+ await chmod3(temporaryPath, 384);
605
707
  await rename2(temporaryPath, tokenPath);
606
- await chmod2(tokenPath, 384);
708
+ await chmod3(tokenPath, 384);
607
709
  }
608
710
  async function readLoopbackToken(tokenPath) {
609
711
  const token = (await readFile3(tokenPath, "utf8")).trim();
@@ -614,11 +716,11 @@ async function readLoopbackToken(tokenPath) {
614
716
 
615
717
  // dist/node-sidecar-lifecycle.js
616
718
  async function startDetachedProcess(specification) {
617
- await mkdir3(dirname2(specification.logPath), { recursive: true, mode: 448 });
719
+ await mkdir4(dirname2(specification.logPath), { recursive: true, mode: 448 });
618
720
  const log = await open(specification.logPath, "a", 384);
619
- await chmod3(specification.logPath, 384);
721
+ await chmod4(specification.logPath, 384);
620
722
  try {
621
- const child = spawn2(specification.executable, specification.args, {
723
+ const child = spawn3(specification.executable, specification.args, {
622
724
  detached: true,
623
725
  env: specification.env,
624
726
  stdio: ["ignore", log.fd, log.fd]
@@ -794,7 +896,7 @@ function createSidecarServer(options) {
794
896
  body: body.raw
795
897
  });
796
898
  await relayGatewayResponse(res, upstream);
797
- if (upstream.status >= 200 && upstream.status < 300) {
899
+ if (upstream.status >= 200 && upstream.status < 300 && upstream.status !== 202) {
798
900
  await options.ledger.consume(reservation.requestDigest);
799
901
  }
800
902
  } catch (error) {
@@ -841,7 +943,7 @@ function createSidecarServer(options) {
841
943
  }
842
944
 
843
945
  // dist/ticket-ledger.js
844
- import { mkdir as mkdir4, readFile as readFile4, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
946
+ import { mkdir as mkdir5, readFile as readFile4, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
845
947
  import { dirname as dirname3 } from "node:path";
846
948
  function parseLedger(raw) {
847
949
  const parsed = JSON.parse(raw);
@@ -914,7 +1016,7 @@ var TicketLedger = class {
914
1016
  }
915
1017
  }
916
1018
  async write(ledger) {
917
- await mkdir4(dirname3(this.filePath), { recursive: true, mode: 448 });
1019
+ await mkdir5(dirname3(this.filePath), { recursive: true, mode: 448 });
918
1020
  const temporaryPath = `${this.filePath}.tmp`;
919
1021
  await writeFile3(temporaryPath, JSON.stringify(ledger), { encoding: "utf8", mode: 384 });
920
1022
  await rename3(temporaryPath, this.filePath);
@@ -925,7 +1027,7 @@ var TicketLedger = class {
925
1027
  var DEFAULT_GATEWAY_URL = "https://zk-credits-gateway.onrender.com";
926
1028
  var DEFAULT_PORT = 3210;
927
1029
  function stateDirectory() {
928
- return process.env.ZK_CREDITS_HOME || join4(homedir(), ".zk-credits");
1030
+ return process.env.ZK_CREDITS_HOME || join5(homedir(), ".zk-credits");
929
1031
  }
930
1032
  function loopbackBaseUrl() {
931
1033
  const port = Number(process.env.ZK_CREDITS_SIDECAR_PORT || DEFAULT_PORT);
@@ -984,6 +1086,7 @@ async function readHiddenMnemonic() {
984
1086
  }
985
1087
  function printHelp() {
986
1088
  console.log(`Usage:
1089
+ zk-credits cline [cline arguments...]
987
1090
  zk-credits setup codex [--model <model>]
988
1091
  zk-credits codex [codex arguments...]
989
1092
  zk-credits status
@@ -991,10 +1094,10 @@ function printHelp() {
991
1094
  zk-credits serve [--port <port>]
992
1095
  eval "$(zk-credits env)"
993
1096
 
994
- The Codex setup starts the loopback sidecar automatically; daily use is
995
- "zk-credits codex". serve/env remain available for other OpenAI-compatible
996
- clients. Set ZK_CREDITS_MNEMONIC only for a headless process; it is not
997
- persisted by that path.`);
1097
+ "zk-credits cline" is the zero-configuration coding-agent path: it starts the
1098
+ sidecar, configures an isolated OpenAI-compatible Cline profile, and forwards
1099
+ all Cline arguments. The Codex companion remains available through setup codex.
1100
+ Set ZK_CREDITS_MNEMONIC only for a headless process; it is not persisted.`);
998
1101
  }
999
1102
  async function serve(args) {
1000
1103
  const port = readPort(args);
@@ -1076,7 +1179,13 @@ async function main() {
1076
1179
  ensureSidecar: () => ensureSidecarReady(lifecycle),
1077
1180
  isCodexProfileInstalled: () => isCodexProfileInstalled(codexHome),
1078
1181
  isSidecarHealthy: () => lifecycle.isHealthy(),
1079
- launchCodex: (codexArgs) => launchCodexProcess(codexArgs)
1182
+ launchCodex: (codexArgs) => launchCodexProcess(codexArgs),
1183
+ launchCline: (clineArgs, localToken) => launchClineProcess({
1184
+ args: clineArgs,
1185
+ loopbackBaseUrl: loopbackBaseUrl(),
1186
+ localToken,
1187
+ stateDirectory: sidecarHome
1188
+ })
1080
1189
  });
1081
1190
  if (exitCode !== 0)
1082
1191
  process.exitCode = exitCode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zk-credits",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Loopback proof-aware OpenAI transport for ZK Credits.",
5
5
  "type": "module",
6
6
  "engines": {