@polylogicai/polycode 1.1.3 → 1.1.4

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/README.md CHANGED
@@ -1,25 +1,35 @@
1
1
  # polycode
2
2
 
3
- An agentic coding CLI. Runs on your machine with your keys. Every turn is appended to a SHA-256 chained session log on disk, so your history is auditable, replayable, and portable across machines.
3
+ An agentic coding CLI. Runs on your machine, writes a SHA-256 chained session log to disk, and ships with a free hosted tier so you can start in one command. Your history is auditable, replayable, and portable across machines.
4
4
 
5
- ## Install
5
+ ## Install and run
6
6
 
7
- The fastest way to try polycode is with `npx`. It works on macOS, Linux, and Windows from any terminal:
7
+ One command, no setup, any platform:
8
8
 
9
9
  ```bash
10
10
  npx @polylogicai/polycode@latest
11
11
  ```
12
12
 
13
- For repeated use, install it globally:
13
+ That is the entire install. The first run provisions a per-install ID under `~/.polycode/` and opens an interactive session. On the free hosted tier you get 60 turns per hour for free, routed through `polylogicai.com/api/polycode/inference`. For unlimited use, set `GROQ_API_KEY` (see below) and polycode will talk to Groq directly and skip the proxy.
14
+
15
+ For repeated use, install globally:
14
16
 
15
17
  ```bash
16
18
  npm install -g @polylogicai/polycode
17
19
  polycode
18
20
  ```
19
21
 
20
- ## Quick start
22
+ ## One-shot mode
23
+
24
+ Pass your prompt as an argument:
25
+
26
+ ```bash
27
+ npx @polylogicai/polycode@latest "read README.md and summarize it in one sentence"
28
+ ```
29
+
30
+ ## Unlimited use with your own key
21
31
 
22
- Set your Groq API key, then run polycode:
32
+ Grab a free key from `console.groq.com`, then:
23
33
 
24
34
  ```bash
25
35
  # macOS and Linux
@@ -31,22 +41,14 @@ $env:GROQ_API_KEY = "gsk_..."
31
41
  npx @polylogicai/polycode@latest
32
42
  ```
33
43
 
34
- That opens an interactive session. For one-shot mode, pass your prompt as an argument:
35
-
36
- ```bash
37
- npx @polylogicai/polycode@latest "read README.md and summarize it in one sentence"
38
- ```
39
-
40
- A free Groq API key is available at `console.groq.com`.
41
-
42
44
  ## Configuration
43
45
 
44
46
  polycode reads configuration from environment variables and optionally from a `~/.polycode/secrets.env` file (chmod 600 recommended).
45
47
 
46
48
  | Variable | Purpose | Required |
47
49
  |---|---|---|
48
- | `GROQ_API_KEY` | Primary inference key for tool-use and reasoning | yes |
49
- | `ANTHROPIC_API_KEY` | Optional high-quality tier for long sessions | no |
50
+ | `GROQ_API_KEY` | Direct Groq access, unlimited. If unset, polycode uses the hosted tier. | no |
51
+ | `ANTHROPIC_API_KEY` | Optional higher-quality compile tier for long sessions | no |
50
52
  | `POLYCODE_MODEL` | Override the default model | no |
51
53
  | `POLYCODE_CWD` | Override the working directory | no |
52
54
 
@@ -109,7 +111,7 @@ Rules live in `~/.polycode/rules.yaml`. You can add your own.
109
111
 
110
112
  - Node.js 20 or newer
111
113
  - macOS, Linux, or Windows
112
- - A Groq API key
114
+ - No API key required for the free hosted tier. Set `GROQ_API_KEY` for unlimited use.
113
115
 
114
116
  ## Documentation and support
115
117
 
package/bin/polycode.mjs CHANGED
@@ -161,8 +161,8 @@ ${C.bold}Usage${C.reset}
161
161
  polycode --verify Verify session history integrity
162
162
 
163
163
  ${C.bold}Environment${C.reset}
164
- GROQ_API_KEY Required (free tier at console.groq.com)
165
- ANTHROPIC_API_KEY Optional high-quality tier for long sessions
164
+ GROQ_API_KEY Optional. If unset, polycode uses the free hosted tier (60 turns/hour) via polylogicai.com. Set a free key from console.groq.com for unlimited use.
165
+ ANTHROPIC_API_KEY Optional higher-quality compile tier for long sessions
166
166
  POLYCODE_MODEL Override the default model
167
167
  POLYCODE_CWD Override the working directory
168
168
 
@@ -259,6 +259,9 @@ async function runOneShot(message, opts) {
259
259
  async function runRepl(opts) {
260
260
  const rl = readline.createInterface({ input: stdin, output: stdout });
261
261
  stdout.write(BANNER + '\n');
262
+ if (opts.hostedMode) {
263
+ stdout.write(`${C.dim}hosted tier: 60 turns/hour via polylogicai.com. Set GROQ_API_KEY for unlimited use.${C.reset}\n`);
264
+ }
262
265
  if (opts.projectContextPath) {
263
266
  stdout.write(`${C.dim}project context: ${opts.projectContextPath}${C.reset}\n`);
264
267
  }
@@ -298,11 +301,12 @@ async function main() {
298
301
 
299
302
  const configDir = resolveConfigDir();
300
303
 
301
- const apiKey = env.GROQ_API_KEY;
302
- if (!apiKey) {
303
- stdout.write(`${C.red}polycode error${C.reset}: GROQ_API_KEY is not set.\n`);
304
- exit(1);
305
- }
304
+ // Zero-config mode: if no GROQ_API_KEY is present, run through the Polylogic
305
+ // hosted inference proxy at polylogicai.com/api/polycode/inference. Users
306
+ // who want unlimited access or their own billing can set GROQ_API_KEY to a
307
+ // free key from console.groq.com and the CLI will talk to Groq directly.
308
+ const apiKey = env.GROQ_API_KEY || '';
309
+ const hostedMode = !apiKey;
306
310
 
307
311
  const rules = loadRules();
308
312
  const model = env.POLYCODE_MODEL || 'moonshotai/kimi-k2-instruct';
@@ -356,10 +360,10 @@ async function main() {
356
360
  canon_path: canonFile,
357
361
  }, hookDir);
358
362
 
359
- const loop = new AgenticLoop({ apiKey, model, rules, projectContext });
363
+ const loop = new AgenticLoop({ apiKey, model, rules, projectContext, hostedMode, version: VERSION });
360
364
  const renderer = createRenderer(stdout, { verbose });
361
365
  const state = { lastTurnTokens: 0, lastCompiler: null };
362
- const opts = { loop, canon, cwd, renderer, state, sessionId, hookDir, verbose, projectContextPath };
366
+ const opts = { loop, canon, cwd, renderer, state, sessionId, hookDir, verbose, projectContextPath, hostedMode };
363
367
 
364
368
  const positional = args.filter((a) => !a.startsWith('--') && args[args.indexOf(a) - 1] !== '--packet');
365
369
  if (positional.length > 0) {
package/lib/agentic.mjs CHANGED
@@ -18,6 +18,7 @@ import { compilePacket } from './compiler.mjs';
18
18
  import { mintCommitment } from './commitment.mjs';
19
19
  import { ensureActiveIntent } from './intent.mjs';
20
20
  import { scrubSecrets } from './witness/secret-scrubber.mjs';
21
+ import { createHostedClient } from './polycode-hosted-client.mjs';
21
22
 
22
23
  // Cross-platform directory walker. Pure Node.js, no shell-out. Skips common
23
24
  // noise directories (node_modules, .git, etc.) by default. Yields absolute
@@ -363,17 +364,21 @@ function recoverInlineToolCalls(content) {
363
364
  }
364
365
 
365
366
  export class AgenticLoop {
366
- constructor({ apiKey, model, logger, rules, projectContext } = {}) {
367
+ constructor({ apiKey, model, logger, rules, projectContext, hostedMode, version } = {}) {
367
368
  this.apiKey = apiKey;
368
369
  this.defaultModel = model || DEFAULT_MODEL;
369
370
  this.logger = logger || console;
370
371
  this.rules = rules || {};
371
372
  this.systemPrompt = buildSystemPrompt(projectContext);
373
+ this.hostedMode = Boolean(hostedMode) || !(apiKey || process.env.GROQ_API_KEY);
374
+ this.version = version || 'unknown';
372
375
  }
373
376
 
374
377
  async runTurn({ canon, userMessage, cwd, onEvent, maxIterations }) {
375
378
  const start = Date.now();
376
- const groq = new Groq({ apiKey: this.apiKey || process.env.GROQ_API_KEY });
379
+ const groq = this.hostedMode
380
+ ? createHostedClient({ version: this.version })
381
+ : new Groq({ apiKey: this.apiKey || process.env.GROQ_API_KEY });
377
382
  let model = this.defaultModel;
378
383
  let didFallback = false;
379
384
  const limit = maxIterations || DEFAULT_MAX_ITERATIONS;
@@ -0,0 +1,100 @@
1
+ // lib/polycode-hosted-client.mjs
2
+ // Hosted inference client for users who have not provided a GROQ_API_KEY.
3
+ // Posts chat-completions requests to the Polylogic proxy at
4
+ // polylogicai.com/api/polycode/inference, which forwards them to Groq using
5
+ // a server-side key. Rate limited to 60 turns per install per hour. Power
6
+ // users can skip the proxy by setting GROQ_API_KEY, in which case the agent
7
+ // talks to Groq directly.
8
+ //
9
+ // The returned object mimics the subset of the groq-sdk surface that the
10
+ // agentic loop uses (chat.completions.create), so the call sites do not need
11
+ // to know which backend is in play.
12
+
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { join } from 'node:path';
16
+ import { randomUUID } from 'node:crypto';
17
+
18
+ const DEFAULT_PROXY_URL = 'https://polylogicai.com/api/polycode/inference';
19
+ const CONFIG_DIR = join(homedir(), '.polycode');
20
+ const INSTALL_ID_PATH = join(CONFIG_DIR, 'install_id');
21
+
22
+ // Produce a stable per-install identifier. Created on first run, persisted
23
+ // to ~/.polycode/install_id so subsequent runs reuse the same ID for rate
24
+ // limiting. The file contains a bare UUID with no PII.
25
+ export function getOrCreateInstallId() {
26
+ try {
27
+ if (existsSync(INSTALL_ID_PATH)) {
28
+ const existing = readFileSync(INSTALL_ID_PATH, 'utf8').trim();
29
+ if (/^[A-Za-z0-9_-]{8,64}$/.test(existing)) return existing;
30
+ }
31
+ } catch {
32
+ // fall through and regenerate
33
+ }
34
+ if (!existsSync(CONFIG_DIR)) {
35
+ mkdirSync(CONFIG_DIR, { recursive: true });
36
+ }
37
+ const fresh = randomUUID();
38
+ try {
39
+ writeFileSync(INSTALL_ID_PATH, fresh + '\n', { mode: 0o600 });
40
+ } catch {
41
+ // non-fatal; rate limiting will fall back to per-IP
42
+ }
43
+ return fresh;
44
+ }
45
+
46
+ // Minimal Groq-SDK-shaped client that posts every request to the Polylogic
47
+ // proxy. The shape matches what lib/agentic.mjs calls: an object with a
48
+ // `chat.completions.create(payload)` method returning an OpenAI-compatible
49
+ // chat completion object.
50
+ export function createHostedClient({ version, proxyUrl } = {}) {
51
+ const url = proxyUrl || process.env.POLYCODE_PROXY_URL || DEFAULT_PROXY_URL;
52
+ const installId = getOrCreateInstallId();
53
+ const headers = {
54
+ 'Content-Type': 'application/json',
55
+ 'X-Polycode-Install-ID': installId,
56
+ 'X-Polycode-Version': version || 'unknown',
57
+ };
58
+
59
+ async function createCompletion(payload) {
60
+ let res;
61
+ try {
62
+ res = await fetch(url, {
63
+ method: 'POST',
64
+ headers,
65
+ body: JSON.stringify(payload),
66
+ });
67
+ } catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ const wrapped = new Error(
70
+ `polycode hosted inference unreachable at ${url}: ${msg}. Set GROQ_API_KEY to a free key from console.groq.com to bypass the proxy.`
71
+ );
72
+ wrapped.cause = err;
73
+ throw wrapped;
74
+ }
75
+
76
+ const text = await res.text();
77
+ if (!res.ok) {
78
+ let parsed;
79
+ try { parsed = JSON.parse(text); } catch { parsed = null; }
80
+ const serverMsg = parsed?.error || text || `HTTP ${res.status}`;
81
+ throw new Error(`polycode hosted inference error (${res.status}): ${serverMsg}`);
82
+ }
83
+
84
+ try {
85
+ return JSON.parse(text);
86
+ } catch (err) {
87
+ throw new Error(`polycode hosted inference returned non-JSON body: ${text.slice(0, 200)}`);
88
+ }
89
+ }
90
+
91
+ return {
92
+ chat: {
93
+ completions: {
94
+ create: createCompletion,
95
+ },
96
+ },
97
+ _proxyUrl: url,
98
+ _installId: installId,
99
+ };
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polylogicai/polycode",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "An agentic coding CLI. Runs on your machine with your keys. Every turn is appended to a SHA-256 chained session log, so your history is auditable, replayable, and portable.",
5
5
  "type": "module",
6
6
  "main": "bin/polycode.mjs",