@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 +19 -17
- package/bin/polycode.mjs +13 -9
- package/lib/agentic.mjs +7 -2
- package/lib/polycode-hosted-client.mjs +100 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
# polycode
|
|
2
2
|
|
|
3
|
-
An agentic coding CLI. Runs on your machine
|
|
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
|
-
|
|
7
|
+
One command, no setup, any platform:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx @polylogicai/polycode@latest
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
For
|
|
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
|
-
##
|
|
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
|
-
|
|
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` |
|
|
49
|
-
| `ANTHROPIC_API_KEY` | Optional
|
|
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
|
-
-
|
|
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
|
|
165
|
-
ANTHROPIC_API_KEY Optional
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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 =
|
|
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
|
+
"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",
|