product-console-companion 0.2.0 → 0.4.0
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 +13 -0
- package/dist/api-key-caller.js +31 -6
- package/dist/claude-cli-caller.js +21 -18
- package/dist/cli.js +71 -13
- package/dist/config.js +24 -1
- package/dist/http-client.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,19 @@ Running `connect` again for a different `--connection`/`--path` (same `--url`/`-
|
|
|
55
55
|
second connection to the same local config; one companion process can serve several repos, each
|
|
56
56
|
with its own backend choice.
|
|
57
57
|
|
|
58
|
+
To pair this machine before any repo is connected, omit `--connection` and `--path`:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx product-console-companion connect --url https://console.fynchconsulting.com --token <publicId>.<secret> [--api-key sk-ant-...]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This runs your workspace's PRD-only setup AI calls when there is no repo yet. `run` then polls
|
|
65
|
+
for workspace-level tasks in addition to any per-repo connections configured on this machine.
|
|
66
|
+
Pass `--backend cli` to use your Claude subscription for these repo-less tasks too. This mode
|
|
67
|
+
runs with zero filesystem access (stricter than the repo-scoped `cli` backend, because there is
|
|
68
|
+
nothing to read). PDF-format PRDs cannot be delivered to the `cli` backend yet; use `api-key`,
|
|
69
|
+
or re-upload the PRD as `.md`, `.txt`, or `.docx`.
|
|
70
|
+
|
|
58
71
|
**`--backend api-key`** (default) needs an Anthropic API key (get one free at
|
|
59
72
|
console.anthropic.com) — either pass it inline with `--api-key sk-ant-...` (saved to this
|
|
60
73
|
package's own local config file, `~/.product-console-companion/config.json`, permissions locked
|
package/dist/api-key-caller.js
CHANGED
|
@@ -160,6 +160,28 @@ export async function runGenerateSpecBatchTask(client, task, repoPath) {
|
|
|
160
160
|
const payload = task.payload;
|
|
161
161
|
const spec = payload.spec;
|
|
162
162
|
const moduleIds = payload.moduleIds;
|
|
163
|
+
if (payload.connectionless) {
|
|
164
|
+
// The PRD is already in spec.stableText (stableTextForLocalDispatch); there is nothing local to read.
|
|
165
|
+
const content = [];
|
|
166
|
+
if (spec.stableText)
|
|
167
|
+
content.push({ type: 'text', text: spec.stableText, cache_control: { type: 'ephemeral' } });
|
|
168
|
+
content.push({ type: 'text', text: spec.volatileText });
|
|
169
|
+
const writeStream = client.messages.stream({
|
|
170
|
+
model: spec.model,
|
|
171
|
+
max_tokens: spec.maxTokens,
|
|
172
|
+
thinking: { type: 'adaptive' },
|
|
173
|
+
output_config: spec.schema ? { effort: spec.effort, format: { type: 'json_schema', schema: spec.schema } } : { effort: spec.effort },
|
|
174
|
+
system: spec.system,
|
|
175
|
+
messages: [{ role: 'user', content }],
|
|
176
|
+
});
|
|
177
|
+
const writeMsg = await writeStream.finalMessage();
|
|
178
|
+
if (writeMsg.stop_reason === 'refusal')
|
|
179
|
+
throw new Error('The model declined to write specs for this batch.');
|
|
180
|
+
const writeText = writeMsg.content.find((b) => b.type === 'text');
|
|
181
|
+
if (!writeText || writeText.type !== 'text')
|
|
182
|
+
throw new Error('No specs returned.');
|
|
183
|
+
return { result: JSON.parse(writeText.text), filesRead: [] };
|
|
184
|
+
}
|
|
163
185
|
const allFiles = await listRepoFiles(repoPath);
|
|
164
186
|
console.log(` scanning ${allFiles.length} files in ${repoPath} for ${moduleIds.length} module(s)…`);
|
|
165
187
|
// Pass 1: which files relate to each module in this batch?
|
|
@@ -236,10 +258,12 @@ in the same order as given.`;
|
|
|
236
258
|
* server/index.ts's completeAiJob payloads for /api/setup/skeleton and
|
|
237
259
|
* /api/module-suggestions/scan — neither includes it), so there's nothing meaningful to report.
|
|
238
260
|
*/
|
|
239
|
-
async function runShallowRepoTask(client, spec, repoPath) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
261
|
+
async function runShallowRepoTask(client, spec, repoPath, connectionless = false) {
|
|
262
|
+
let repoSection = '';
|
|
263
|
+
if (!connectionless) {
|
|
264
|
+
const repoCtx = await buildLocalRepoContext(repoPath);
|
|
265
|
+
console.log(` scanning ${repoCtx.fileList.length} files, README ${repoCtx.readme ? 'found' : 'not found'}, CHANGELOG ${repoCtx.changelog ? 'found' : 'not found'}, package.json ${repoCtx.packageJson ? 'found' : 'not found'}`);
|
|
266
|
+
repoSection = `
|
|
243
267
|
# Repository context (your local checkout)
|
|
244
268
|
|
|
245
269
|
## File tree (${repoCtx.fileList.length} files)
|
|
@@ -254,13 +278,14 @@ ${repoCtx.changelog ?? '(none found)'}
|
|
|
254
278
|
## package.json
|
|
255
279
|
${repoCtx.packageJson ?? '(none found)'}
|
|
256
280
|
`.trim();
|
|
281
|
+
}
|
|
257
282
|
const content = [];
|
|
258
283
|
if (spec.stableDocumentBase64) {
|
|
259
284
|
content.push({ type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: spec.stableDocumentBase64 } });
|
|
260
285
|
}
|
|
261
286
|
if (spec.stableText)
|
|
262
287
|
content.push({ type: 'text', text: spec.stableText, cache_control: { type: 'ephemeral' } });
|
|
263
|
-
content.push({ type: 'text', text: `${spec.volatileText}\n\n${repoSection}` });
|
|
288
|
+
content.push({ type: 'text', text: connectionless ? spec.volatileText : `${spec.volatileText}\n\n${repoSection}` });
|
|
264
289
|
const stream = client.messages.stream({
|
|
265
290
|
model: spec.model,
|
|
266
291
|
max_tokens: spec.maxTokens,
|
|
@@ -279,7 +304,7 @@ ${repoCtx.packageJson ?? '(none found)'}
|
|
|
279
304
|
}
|
|
280
305
|
export async function runProposeSkeletonTask(client, task, repoPath) {
|
|
281
306
|
const payload = task.payload;
|
|
282
|
-
return runShallowRepoTask(client, payload.spec, repoPath);
|
|
307
|
+
return runShallowRepoTask(client, payload.spec, repoPath, payload.connectionless);
|
|
283
308
|
}
|
|
284
309
|
export async function runProposeNewCapabilitiesTask(client, task, repoPath) {
|
|
285
310
|
const payload = task.payload;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* during development, not guessed from --help text alone — see the Phase 2 PR description.
|
|
12
12
|
*/
|
|
13
13
|
import spawn from 'cross-spawn';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
14
15
|
import { createInterface } from 'node:readline';
|
|
15
16
|
import { createSandbox } from './sandbox.js';
|
|
16
17
|
import { appendDiffToVolatile, prepareShipmentAnalysis } from './shipment.js';
|
|
@@ -26,13 +27,18 @@ function normalizeToRepoRelative(filePath, root) {
|
|
|
26
27
|
const r = root.replace(/\\/g, '/');
|
|
27
28
|
return (p.startsWith(r) ? p.slice(r.length) : p).replace(/^\/+/, '');
|
|
28
29
|
}
|
|
29
|
-
/** The actual invocation — task-kind-agnostic.
|
|
30
|
-
* worktree of `repoPath
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
/** The actual invocation — task-kind-agnostic. Repo-scoped calls run against a disposable
|
|
31
|
+
* sandbox worktree of `repoPath`; connection-less calls grant no tools and need no sandbox.
|
|
32
|
+
* Returns whatever structured_output the model produced plus every file it actually read via
|
|
33
|
+
* the Read tool. Every exported per-kind function below is a thin wrapper over this. */
|
|
34
|
+
async function runViaCli(spec, maxBudgetUsd, repoPath, connectionless = false) {
|
|
35
|
+
if (spec.stableDocumentBase64) {
|
|
36
|
+
throw new Error("This task includes a PDF PRD, which the cli backend can't read yet. Pair with " +
|
|
37
|
+
'--backend api-key instead, or re-upload the PRD as .md/.txt/.docx.');
|
|
38
|
+
}
|
|
39
|
+
const sandbox = connectionless
|
|
40
|
+
? { path: tmpdir(), cleanup: async () => { } }
|
|
41
|
+
: await createSandbox(repoPath);
|
|
36
42
|
try {
|
|
37
43
|
const args = [
|
|
38
44
|
'-p',
|
|
@@ -43,10 +49,7 @@ async function runViaCli(spec, maxBudgetUsd, repoPath) {
|
|
|
43
49
|
'--verbose',
|
|
44
50
|
...(spec.schema ? ['--json-schema', JSON.stringify(spec.schema)] : []),
|
|
45
51
|
'--restricted',
|
|
46
|
-
'--tools',
|
|
47
|
-
'Read,Grep,Glob',
|
|
48
|
-
'--add-dir',
|
|
49
|
-
sandbox.path,
|
|
52
|
+
...(connectionless ? ['--tools', ''] : ['--tools', 'Read,Grep,Glob', '--add-dir', sandbox.path]),
|
|
50
53
|
'--model',
|
|
51
54
|
spec.model,
|
|
52
55
|
'--effort',
|
|
@@ -66,6 +69,8 @@ async function runViaCli(spec, maxBudgetUsd, repoPath) {
|
|
|
66
69
|
let stderrTail = '';
|
|
67
70
|
let timedOut = false;
|
|
68
71
|
const child = spawn('claude', args, { cwd: sandbox.path });
|
|
72
|
+
if (spec.stableText)
|
|
73
|
+
child.stdin.write(spec.stableText + '\n\n');
|
|
69
74
|
child.stdin.write(spec.volatileText);
|
|
70
75
|
child.stdin.end();
|
|
71
76
|
// stdout/stderr are only null when stdio is explicitly configured away from 'pipe' — the
|
|
@@ -135,19 +140,17 @@ export async function runVerifyModuleTaskViaCli(task, repoPath) {
|
|
|
135
140
|
return runViaCli(payload.spec, task.maxBudgetUsd, repoPath);
|
|
136
141
|
}
|
|
137
142
|
/** moduleIds isn't needed here the way api-key-caller.ts's batch path needs it (to structure its
|
|
138
|
-
* own file-select pass) —
|
|
139
|
-
* alone, which already names every module in the batch. */
|
|
143
|
+
* own file-select pass) — spec.volatileText already names every module in the batch. */
|
|
140
144
|
export async function runGenerateSpecBatchTaskViaCli(task, repoPath) {
|
|
141
145
|
const payload = task.payload;
|
|
142
|
-
return runViaCli(payload.spec, task.maxBudgetUsd, repoPath);
|
|
146
|
+
return runViaCli(payload.spec, task.maxBudgetUsd, repoPath, payload.connectionless);
|
|
143
147
|
}
|
|
144
148
|
/** propose_skeleton and propose_new_capabilities are "shallow whole-repo" tasks (see
|
|
145
|
-
* api-key-caller.ts's own comment on the pair)
|
|
146
|
-
*
|
|
147
|
-
* itself, same as verify_module/generate_spec_batch, so these are exactly as thin as those. */
|
|
149
|
+
* api-key-caller.ts's own comment on the pair). Repo-scoped calls let the model explore via
|
|
150
|
+
* --add-dir plus Read/Grep/Glob; a connection-less skeleton call has no repo to explore. */
|
|
148
151
|
export async function runProposeSkeletonTaskViaCli(task, repoPath) {
|
|
149
152
|
const payload = task.payload;
|
|
150
|
-
return runViaCli(payload.spec, task.maxBudgetUsd, repoPath);
|
|
153
|
+
return runViaCli(payload.spec, task.maxBudgetUsd, repoPath, payload.connectionless);
|
|
151
154
|
}
|
|
152
155
|
export async function runProposeNewCapabilitiesTaskViaCli(task, repoPath) {
|
|
153
156
|
const payload = task.payload;
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import Anthropic from '@anthropic-ai/sdk';
|
|
3
3
|
import spawn from 'cross-spawn';
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
|
-
import { addConnection, configPath, loadConfig } from './config.js';
|
|
5
|
+
import { addConnection, configPath, enableWorkspacePolling, loadConfig } from './config.js';
|
|
6
6
|
import { CompanionClient, CompanionHttpError } from './http-client.js';
|
|
7
7
|
import { assertIsRepo, currentCommit } from './local-scan.js';
|
|
8
8
|
import { runAnalyseShipmentTask, runDesignPromptTask, runGenerateSpecBatchTask, runProposeNewCapabilitiesTask, runProposeSkeletonTask, runVerifyModuleTask } from './api-key-caller.js';
|
|
@@ -41,19 +41,17 @@ async function cmdConnect(argv) {
|
|
|
41
41
|
const url = f.url?.replace(/\/+$/, '');
|
|
42
42
|
const token = f.token;
|
|
43
43
|
const connectionId = f.connection;
|
|
44
|
-
// --path is optional — defaults to wherever this command is actually run from, so "open a
|
|
45
|
-
// terminal in your project's folder, paste this" just works without typing a path at all.
|
|
46
|
-
const repoPath = resolve(f.path || '.');
|
|
47
|
-
const backend = (f.backend || 'api-key');
|
|
48
44
|
const apiKey = f['api-key'];
|
|
45
|
+
const backend = (f.backend || 'api-key');
|
|
49
46
|
// Pairing on its own does nothing useful — see cmdRun below — so this defaults to starting
|
|
50
47
|
// right away, which is what someone following the app's own copy-pasted command wants: one
|
|
51
48
|
// paste, and it's working. --no-run is the escape hatch for pairing a second/third project
|
|
52
49
|
// before starting one shared `run` for all of them (see the README's own multi-connection note).
|
|
53
50
|
const shouldRunAfter = f['no-run'] !== 'true';
|
|
54
|
-
if (!url || !token
|
|
51
|
+
if (!url || !token) {
|
|
55
52
|
console.error('Usage: product-console-companion connect --url <your Console link> --token <publicId.secret> --connection <connectionId> ' +
|
|
56
|
-
'[--path <folder — defaults to the folder you\'re in right now>] [--backend api-key|cli] [--api-key <your Anthropic key>] [--no-run]'
|
|
53
|
+
'[--path <folder — defaults to the folder you\'re in right now>] [--backend api-key|cli] [--api-key <your Anthropic key>] [--no-run]\n' +
|
|
54
|
+
' or: product-console-companion connect --url <your Console link> --token <publicId.secret> [--backend api-key|cli] [--api-key <your Anthropic key>] [--no-run]');
|
|
57
55
|
process.exitCode = 1;
|
|
58
56
|
return;
|
|
59
57
|
}
|
|
@@ -62,6 +60,39 @@ async function cmdConnect(argv) {
|
|
|
62
60
|
process.exitCode = 1;
|
|
63
61
|
return;
|
|
64
62
|
}
|
|
63
|
+
if (!connectionId) {
|
|
64
|
+
if (backend === 'cli') {
|
|
65
|
+
const version = await checkClaudeCliAvailable();
|
|
66
|
+
if (!version) {
|
|
67
|
+
console.error('This needs Claude Code installed and you logged into it first: npm install -g @anthropic-ai/claude-code, then run `claude` and follow the login prompt.');
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
console.log(`Found Claude Code: ${version}`);
|
|
72
|
+
}
|
|
73
|
+
const client = new CompanionClient(url, token);
|
|
74
|
+
try {
|
|
75
|
+
await client.whoami();
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error(`Hmm, that didn't work — could not verify your token against ${url}. Double check you copied the ` +
|
|
79
|
+
`WHOLE thing, including the part after the dot.`, err instanceof Error ? err.message : err);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
enableWorkspacePolling({ url, token, apiKey, backend });
|
|
84
|
+
console.log(`Connected! This computer will now run AI calls for this workspace's setup (PRD-only) tasks. (Saved to ${configPath}.)`);
|
|
85
|
+
if (!shouldRunAfter) {
|
|
86
|
+
console.log('Run "product-console-companion run" whenever you\'re ready to start.');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
console.log('Starting now — Ctrl+C any time to stop.\n');
|
|
90
|
+
await cmdRun();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// --path is optional — defaults to wherever this command is actually run from, so "open a
|
|
94
|
+
// terminal in your project's folder, paste this" just works without typing a path at all.
|
|
95
|
+
const repoPath = resolve(f.path || '.');
|
|
65
96
|
try {
|
|
66
97
|
await assertIsRepo(repoPath);
|
|
67
98
|
}
|
|
@@ -155,15 +186,16 @@ async function runOneTask(anthropic, backend, client, repoPath, task) {
|
|
|
155
186
|
}
|
|
156
187
|
async function cmdRun() {
|
|
157
188
|
const config = loadConfig();
|
|
158
|
-
if (!config || config.connections.length === 0) {
|
|
189
|
+
if (!config || (config.connections.length === 0 && !config.pollWorkspaceTasks)) {
|
|
159
190
|
console.error(`Nothing set up yet — ${configPath} is missing or empty. Run "connect" first.`);
|
|
160
191
|
process.exitCode = 1;
|
|
161
192
|
return;
|
|
162
193
|
}
|
|
163
|
-
//
|
|
164
|
-
// configured to run, not every prerequisite either backend could
|
|
165
|
-
const
|
|
166
|
-
const
|
|
194
|
+
// Repo backends are chosen per connection; workspace-level tasks have their own choice. Only
|
|
195
|
+
// require what's actually configured to run, not every prerequisite either backend could need.
|
|
196
|
+
const workspaceBackend = config.workspaceBackend ?? 'api-key';
|
|
197
|
+
const needsApiKey = (config.pollWorkspaceTasks && workspaceBackend !== 'cli') || config.connections.some((c) => c.backend !== 'cli');
|
|
198
|
+
const needsCli = (config.pollWorkspaceTasks && workspaceBackend === 'cli') || config.connections.some((c) => c.backend === 'cli');
|
|
167
199
|
// config.apiKey (set via `connect --api-key ...`) takes priority over the environment variable
|
|
168
200
|
// — the easier path for someone who'd rather paste their key once into a command than learn
|
|
169
201
|
// what an environment variable is and how to set one on their OS. Both are supported; neither
|
|
@@ -192,11 +224,18 @@ async function cmdRun() {
|
|
|
192
224
|
const anthropic = needsApiKey ? new Anthropic({ apiKey }) : null;
|
|
193
225
|
const client = new CompanionClient(config.url, config.token);
|
|
194
226
|
await client.whoami();
|
|
195
|
-
|
|
227
|
+
const projectCount = config.connections.length;
|
|
228
|
+
const watching = config.pollWorkspaceTasks
|
|
229
|
+
? `${projectCount > 0 ? `${projectCount} project${projectCount === 1 ? '' : 's'} and ` : ''}workspace-level tasks`
|
|
230
|
+
: `${projectCount} project${projectCount === 1 ? '' : 's'}`;
|
|
231
|
+
console.log(`Connected to ${config.url}. Watching ${watching}:`);
|
|
196
232
|
for (const c of config.connections) {
|
|
197
233
|
const sha = await currentCommit(c.repoPath).catch(() => '(unknown)');
|
|
198
234
|
console.log(` - ${c.connectionId} → ${c.repoPath} @ ${sha.slice(0, 7)} (${c.backend})`);
|
|
199
235
|
}
|
|
236
|
+
if (config.pollWorkspaceTasks) {
|
|
237
|
+
console.log(` - workspace-level (connection-less / PRD-only) tasks (${workspaceBackend})`);
|
|
238
|
+
}
|
|
200
239
|
console.log('Waiting for work — leave this running. Ctrl+C any time to stop.');
|
|
201
240
|
let stopping = false;
|
|
202
241
|
process.on('SIGINT', () => {
|
|
@@ -227,6 +266,25 @@ async function cmdRun() {
|
|
|
227
266
|
claimedAny = true;
|
|
228
267
|
await runOneTask(anthropic, c.backend, client, c.repoPath, claim.task);
|
|
229
268
|
}
|
|
269
|
+
if (!stopping && config.pollWorkspaceTasks) {
|
|
270
|
+
let claim;
|
|
271
|
+
try {
|
|
272
|
+
claim = await client.claimNext();
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
if (err instanceof CompanionHttpError && err.status === 401) {
|
|
276
|
+
console.error('Token rejected (revoked?) — stopping. Pair a new token and reconnect.');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
console.error('Workspace-level poll failed:', err instanceof Error ? err.message : err);
|
|
280
|
+
claim = { task: null };
|
|
281
|
+
}
|
|
282
|
+
if (claim.task) {
|
|
283
|
+
claimedAny = true;
|
|
284
|
+
// Connection-less task kinds do not read a repo; cwd is a harmless placeholder.
|
|
285
|
+
await runOneTask(anthropic, workspaceBackend, client, process.cwd(), claim.task);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
230
288
|
if (!claimedAny && !stopping)
|
|
231
289
|
await new Promise((r) => setTimeout(r, IDLE_POLL_MS));
|
|
232
290
|
}
|
package/dist/config.js
CHANGED
|
@@ -27,7 +27,30 @@ export function addConnection(args) {
|
|
|
27
27
|
const existing = loadConfig();
|
|
28
28
|
const connections = (existing?.connections ?? []).filter((c) => c.connectionId !== args.connection.connectionId);
|
|
29
29
|
connections.push(args.connection);
|
|
30
|
-
const config = {
|
|
30
|
+
const config = {
|
|
31
|
+
url: args.url,
|
|
32
|
+
token: args.token,
|
|
33
|
+
connections,
|
|
34
|
+
apiKey: args.apiKey || existing?.apiKey,
|
|
35
|
+
pollWorkspaceTasks: existing?.pollWorkspaceTasks,
|
|
36
|
+
workspaceBackend: existing?.workspaceBackend,
|
|
37
|
+
};
|
|
38
|
+
save(config);
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
/** Records that this companion should also poll for workspace-level (connection-less) tasks.
|
|
42
|
+
* Additive: keeps any existing connections and apiKey; only sets url/token if not already set
|
|
43
|
+
* (or if they differ — same rule as addConnection). */
|
|
44
|
+
export function enableWorkspacePolling(args) {
|
|
45
|
+
const existing = loadConfig();
|
|
46
|
+
const config = {
|
|
47
|
+
url: args.url,
|
|
48
|
+
token: args.token,
|
|
49
|
+
connections: existing?.connections ?? [],
|
|
50
|
+
apiKey: args.apiKey || existing?.apiKey,
|
|
51
|
+
pollWorkspaceTasks: true,
|
|
52
|
+
workspaceBackend: args.backend || existing?.workspaceBackend,
|
|
53
|
+
};
|
|
31
54
|
save(config);
|
|
32
55
|
return config;
|
|
33
56
|
}
|
package/dist/http-client.js
CHANGED
|
@@ -26,7 +26,7 @@ export class CompanionClient {
|
|
|
26
26
|
return this.call('/api/companion/whoami');
|
|
27
27
|
}
|
|
28
28
|
claimNext(connectionId) {
|
|
29
|
-
return this.call(`/api/companion/tasks/next?connectionId=${encodeURIComponent(connectionId)}`);
|
|
29
|
+
return this.call(connectionId ? `/api/companion/tasks/next?connectionId=${encodeURIComponent(connectionId)}` : '/api/companion/tasks/next');
|
|
30
30
|
}
|
|
31
31
|
heartbeat(taskId) {
|
|
32
32
|
return this.call(`/api/companion/tasks/${encodeURIComponent(taskId)}/heartbeat`, { method: 'POST' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "product-console-companion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Local-first BYOS companion for the Fynch Product Console — reads code and runs AI calls from your own machine instead of the hosted server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|