@crouton-kit/crouter 0.3.68 → 0.3.78
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 +1 -1
- package/dist/builtin-memory/00-runtime-base.md +3 -10
- package/dist/builtin-memory/04-base-worker.md +18 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +639 -741
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +241 -58
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +365 -94
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
- package/dist/builtin-views/canvas/tui.mjs +8 -9
- package/dist/builtin-views/chat/tui.mjs +12 -12
- package/dist/builtin-views/git-pr/tui.mjs +7 -8
- package/dist/builtin-views/inbox/tui.mjs +27 -41
- package/dist/builtin-views/linkedin/tui.mjs +23 -37
- package/dist/builtin-views/prompt-review/tui.mjs +11 -11
- package/dist/builtin-views/settings/tui.mjs +11 -11
- package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -0
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -7
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
- package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/mermaid-render.test.js +20 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
- package/dist/clients/attach/attach-cmd.d.ts +1 -1
- package/dist/clients/attach/attach-cmd.js +733 -730
- package/dist/clients/attach/auth-pickers.d.ts +12 -0
- package/dist/clients/attach/auth-pickers.js +11 -0
- package/dist/clients/attach/canvas-panels.js +2 -3
- package/dist/clients/attach/chat-view.d.ts +7 -8
- package/dist/clients/attach/chat-view.js +119 -79
- package/dist/clients/attach/edit-diff-render.d.ts +6 -0
- package/dist/clients/attach/edit-diff-render.js +263 -0
- package/dist/clients/attach/input-controller.d.ts +28 -2
- package/dist/clients/attach/input-controller.js +38 -5
- package/dist/clients/attach/mermaid-render.js +5 -2
- package/dist/clients/attach/pickers.d.ts +8 -7
- package/dist/clients/attach/pickers.js +13 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +128 -7
- package/dist/clients/attach/titled-editor.d.ts +12 -1
- package/dist/clients/attach/titled-editor.js +103 -8
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/commands/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +12 -5
- package/dist/commands/profile/new.js +30 -5
- package/dist/commands/surface-tmux-spread.js +1 -3
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
- package/dist/commands/sys/config.js +18 -21
- package/dist/commands/sys/setup-core.d.ts +12 -1
- package/dist/commands/sys/setup-core.js +25 -3
- package/dist/commands/sys/setup.js +166 -20
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
- package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
- package/dist/core/__tests__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +9 -6
- package/dist/core/__tests__/full/cascade-close.test.js +5 -2
- package/dist/core/__tests__/full/dead-pane-regression.test.js +6 -3
- package/dist/core/__tests__/full/detach-focus.test.js +10 -5
- package/dist/core/__tests__/full/human-new-window-regression.test.js +6 -3
- package/dist/core/__tests__/full/review-render-pane-regression.test.js +2 -2
- package/dist/core/__tests__/helpers/harness.d.ts +1 -0
- package/dist/core/__tests__/helpers/harness.js +28 -4
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -4
- package/dist/core/__tests__/review-model-floor.test.js +1 -1
- package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
- package/dist/core/__tests__/stream-watchdog.test.js +70 -0
- package/dist/core/__tests__/tmux-surface.test.js +72 -0
- package/dist/core/canvas/__tests__/remote-event-stream.test.js +11 -9
- package/dist/core/canvas/__tests__/render-remote.test.js +7 -4
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +30 -13
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +164 -5
- package/dist/core/canvas/browse/model.d.ts +10 -6
- package/dist/core/canvas/browse/model.js +20 -12
- package/dist/core/canvas/browse/pins.d.ts +4 -0
- package/dist/core/canvas/browse/pins.js +29 -0
- package/dist/core/canvas/browse/render.d.ts +13 -1
- package/dist/core/canvas/browse/render.js +72 -15
- package/dist/core/canvas/nav-model.js +5 -11
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +7 -0
- package/dist/core/canvas/pid.d.ts +14 -10
- package/dist/core/canvas/pid.js +14 -10
- package/dist/core/config.d.ts +53 -2
- package/dist/core/config.js +228 -110
- package/dist/core/fault-classifier.js +1 -1
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +15 -1
- package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
- package/dist/core/keybindings/catalog.d.ts +14 -0
- package/dist/core/keybindings/catalog.js +256 -0
- package/dist/core/keybindings/index.d.ts +5 -0
- package/dist/core/keybindings/index.js +4 -0
- package/dist/core/keybindings/match.d.ts +29 -0
- package/dist/core/keybindings/match.js +99 -0
- package/dist/core/keybindings/persistence.d.ts +14 -0
- package/dist/core/keybindings/persistence.js +31 -0
- package/dist/core/keybindings/resolve.d.ts +14 -0
- package/dist/core/keybindings/resolve.js +305 -0
- package/dist/core/keybindings/types.d.ts +37 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +10 -3
- package/dist/core/predicate.d.ts +5 -3
- package/dist/core/predicate.js +5 -3
- package/dist/core/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +165 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +79 -21
- package/dist/core/runtime/naming.js +3 -3
- package/dist/core/runtime/pi-cli.d.ts +6 -0
- package/dist/core/runtime/pi-cli.js +16 -2
- package/dist/core/runtime/placement.d.ts +2 -2
- package/dist/core/runtime/placement.js +4 -1
- package/dist/core/runtime/promote.js +4 -0
- package/dist/core/runtime/recap.d.ts +3 -3
- package/dist/core/runtime/recap.js +47 -44
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/revive.js +28 -18
- package/dist/core/runtime/session-list-cache.d.ts +23 -0
- package/dist/core/runtime/session-list-cache.js +270 -0
- package/dist/core/runtime/spawn.js +51 -32
- package/dist/core/runtime/stream-watchdog.d.ts +26 -0
- package/dist/core/runtime/stream-watchdog.js +75 -0
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +2 -2
- package/dist/core/runtime/tmux.d.ts +18 -15
- package/dist/core/runtime/tmux.js +216 -110
- package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
- package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
- package/dist/core/tui/host.d.ts +30 -1
- package/dist/core/tui/host.js +148 -34
- package/dist/core/view/__tests__/transport-remote.test.js +9 -6
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/daemon/crtrd-cli.d.ts +1 -1
- package/dist/daemon/crtrd-cli.js +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
- package/dist/pi-extensions/canvas-recap.d.ts +4 -0
- package/dist/pi-extensions/canvas-recap.js +38 -37
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/suppress-experimental-warnings.d.ts +1 -0
- package/dist/suppress-experimental-warnings.js +15 -0
- package/dist/types.d.ts +3 -13
- package/dist/types.js +12 -36
- package/dist/web-client/assets/{index-CnF5r8ky.js → index--md2ylfi.js} +18 -17
- package/dist/web-client/assets/index-BUdm9s9s.css +2 -0
- package/dist/web-client/index.html +3 -3
- package/dist/web-client/sw.js +1 -1
- package/package.json +11 -8
- package/scripts/postinstall.mjs +1 -1
- package/dist/pi-extensions/canvas-nav.d.ts +0 -43
- package/dist/pi-extensions/canvas-nav.js +0 -640
- package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
- package/dist/pi-extensions/widget-order-bus.js +0 -34
- package/dist/web-client/assets/index-BnmSLNLa.css +0 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineLeaf } from '../../core/command.js';
|
|
2
|
-
import {
|
|
2
|
+
import { usage } from '../../core/errors.js';
|
|
3
|
+
import { createProfile, ensureRootProfile, profileRoot } from '../../core/profiles/manifest.js';
|
|
3
4
|
export const newLeaf = defineLeaf({
|
|
4
5
|
name: 'new',
|
|
5
6
|
description: 'create a new profile',
|
|
6
|
-
whenToUse: 'you are establishing a new agent identity — a stable id, a display name, and its own memory store. Pass at most one initial project directory here (the parser has no repeatable-flag support); use `profile project add` afterward for every additional one.',
|
|
7
|
+
whenToUse: 'you are establishing a new agent identity — a stable id, a display name, and its own memory store. Pass at most one initial project directory here (the parser has no repeatable-flag support); use `profile project add` afterward for every additional one. Use `--root` (no name) to provision the canonical stable root profile — the default identity a home falls back to — idempotently, without spawning a node.',
|
|
7
8
|
help: {
|
|
8
9
|
name: 'profile new',
|
|
9
10
|
summary: 'create a profile: `<profiles-root>/<slug>-<id>/` with a manifest + its own `memory/` store',
|
|
@@ -12,8 +13,15 @@ export const newLeaf = defineLeaf({
|
|
|
12
13
|
kind: 'flag',
|
|
13
14
|
name: 'name',
|
|
14
15
|
type: 'string',
|
|
15
|
-
required:
|
|
16
|
-
constraint: 'Display name for the profile. Also slugified into the stable profile id — the id never changes on later rename.',
|
|
16
|
+
required: false,
|
|
17
|
+
constraint: 'Display name for the profile. Also slugified into the stable profile id — the id never changes on later rename. Required unless `--root` is given; mutually exclusive with `--root`.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
kind: 'flag',
|
|
21
|
+
name: 'root',
|
|
22
|
+
type: 'bool',
|
|
23
|
+
required: false,
|
|
24
|
+
constraint: 'Provision the canonical stable root profile (fixed id `root-00000000`, name `root`) — the fallback default identity for a home — instead of a fresh named one. Idempotent: re-running returns the existing root. Node-free (no `node new --root` needed). Mutually exclusive with `--name`/`--project`.',
|
|
17
25
|
},
|
|
18
26
|
{
|
|
19
27
|
kind: 'flag',
|
|
@@ -32,11 +40,28 @@ export const newLeaf = defineLeaf({
|
|
|
32
40
|
{ name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
|
|
33
41
|
],
|
|
34
42
|
outputKind: 'object',
|
|
35
|
-
effects: ['Creates `<profile-dir>/profile.json` and `<profile-dir>/memory
|
|
43
|
+
effects: ['Creates `<profile-dir>/profile.json` and `<profile-dir>/memory/` (the fixed root dir when `--root`).'],
|
|
36
44
|
},
|
|
37
45
|
run: async (input) => {
|
|
38
46
|
const name = input['name'];
|
|
39
47
|
const project = input['project'];
|
|
48
|
+
const root = input['root'] === true;
|
|
49
|
+
if (root) {
|
|
50
|
+
if (name !== undefined || project !== undefined) {
|
|
51
|
+
throw usage('`--root` provisions the canonical stable root profile and takes no `--name`/`--project`.');
|
|
52
|
+
}
|
|
53
|
+
const { profileId, manifest } = ensureRootProfile();
|
|
54
|
+
return {
|
|
55
|
+
profile_id: profileId,
|
|
56
|
+
name: manifest.name,
|
|
57
|
+
path: profileRoot(profileId),
|
|
58
|
+
projects: manifest.projects,
|
|
59
|
+
created_at: manifest.created_at,
|
|
60
|
+
follow_up: `Root profile ensured. Spawn nodes under it with \`--profile ${profileId}\`, or widen its purview with \`crtr profile project add ${profileId} --dir <path>\`.`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (name === undefined)
|
|
64
|
+
throw usage('provide `--name <name>` to create a profile, or `--root` to provision the stable root profile.');
|
|
40
65
|
const { profileId, manifest } = createProfile(name, project !== undefined ? [project] : []);
|
|
41
66
|
return {
|
|
42
67
|
profile_id: profileId,
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// A genuine viewer-placement decision (tile every live viewer in a subtree into
|
|
4
4
|
// one window), so it lives under `surface` alongside view/attach/workspace/web.
|
|
5
|
-
//
|
|
6
|
-
// which shells this leaf directly for the cursor node. It "expands" the graph
|
|
7
|
-
// you are watching: the caller's
|
|
5
|
+
// The command expands the graph being watched: the caller's
|
|
8
6
|
// own viewer pane is broken out into a BRAND-NEW window, then a surface-attach
|
|
9
7
|
// viewer pane is tiled beside it for every broker-alive node in the local
|
|
10
8
|
// subtree (ancestry root → self → descendants), so the user lands on a single
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { after, before, beforeEach, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { configBranch } from '../config.js';
|
|
7
|
+
import { ensureScopeInitialized } from '../../../core/config.js';
|
|
8
|
+
import { resetScopeCache } from '../../../core/scope.js';
|
|
9
|
+
function leaf(name) {
|
|
10
|
+
const found = (configBranch.children ?? []).find((child) => child.name === name);
|
|
11
|
+
if (found === undefined || found.kind !== 'leaf')
|
|
12
|
+
throw new Error(`no leaf '${name}' under sys config`);
|
|
13
|
+
return found;
|
|
14
|
+
}
|
|
15
|
+
let home;
|
|
16
|
+
let realHome;
|
|
17
|
+
before(() => { realHome = process.env['HOME']; });
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
home = mkdtempSync(join(tmpdir(), 'crtr-config-keybindings-'));
|
|
20
|
+
process.env['HOME'] = home;
|
|
21
|
+
});
|
|
22
|
+
after(() => {
|
|
23
|
+
if (realHome === undefined)
|
|
24
|
+
delete process.env['HOME'];
|
|
25
|
+
else
|
|
26
|
+
process.env['HOME'] = realHome;
|
|
27
|
+
});
|
|
28
|
+
test.afterEach(() => rmSync(home, { recursive: true, force: true }));
|
|
29
|
+
test('a new project scope initializes through the config lock', () => {
|
|
30
|
+
const project = mkdtempSync(join(tmpdir(), 'crtr-config-project-'));
|
|
31
|
+
const previous = process.cwd();
|
|
32
|
+
try {
|
|
33
|
+
process.chdir(project);
|
|
34
|
+
resetScopeCache();
|
|
35
|
+
const root = join(project, '.crouter');
|
|
36
|
+
ensureScopeInitialized('project', root);
|
|
37
|
+
assert.equal(JSON.parse(readFileSync(join(root, 'config.json'), 'utf8')).keybindings, undefined);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
process.chdir(previous);
|
|
41
|
+
resetScopeCache();
|
|
42
|
+
rmSync(project, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
test('sys config exposes whole user keybindings read-only and routes edits to setup', async () => {
|
|
46
|
+
mkdirSync(join(home, '.crouter'), { recursive: true });
|
|
47
|
+
writeFileSync(join(home, '.crouter', 'config.json'), JSON.stringify({
|
|
48
|
+
keybindings: { 'crtr.graph.down': ['down'] },
|
|
49
|
+
}));
|
|
50
|
+
const result = await leaf('get').run({ key: 'keybindings', scope: 'user' });
|
|
51
|
+
assert.deepEqual(result['value'], { 'crtr.graph.down': ['down'] });
|
|
52
|
+
await assert.rejects(() => leaf('get').run({ key: 'keybindings.crtr.graph.down', scope: 'user' }), /whole user-scope object/);
|
|
53
|
+
await assert.rejects(() => leaf('set').run({ key: 'keybindings', value: '{}' }), /crtr sys setup/);
|
|
54
|
+
assert.doesNotMatch(JSON.stringify(configBranch), /canvasNav/);
|
|
55
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// config-model-ladders.test.ts — regression coverage for the live Hearth
|
|
2
|
+
// incident where a home's config.json had `openai-codex/gpt-5.6-*` baked into
|
|
3
|
+
// `modelLadders` while the guest's own installed model registry only had
|
|
4
|
+
// `gpt-5.5`. Root cause: both `ensureScopeInitialized` (fresh scope) and
|
|
5
|
+
// `sys config set modelLadders...` (a targeted edit) persisted the FULLY
|
|
6
|
+
// materialized ladder (all 4 strengths x 2 providers) rather than only the
|
|
7
|
+
// genuine override, freezing whatever concrete model ids happened to be
|
|
8
|
+
// compiled into the crouter build that ran at that moment. A later
|
|
9
|
+
// crouter/pi upgrade (or rollback) that changes the registry can then never
|
|
10
|
+
// reach the frozen rungs again.
|
|
11
|
+
import { test, before, after, beforeEach, afterEach } from 'node:test';
|
|
12
|
+
import assert from 'node:assert/strict';
|
|
13
|
+
import { mkdtempSync, rmSync, readFileSync, existsSync } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { ensureScopeInitialized, readConfig, updateConfig } from '../../../core/config.js';
|
|
17
|
+
import { configBranch } from '../config.js';
|
|
18
|
+
function leaf(name) {
|
|
19
|
+
const found = (configBranch.children ?? []).find((c) => c.name === name);
|
|
20
|
+
if (found === undefined || found.kind !== 'leaf')
|
|
21
|
+
throw new Error(`no leaf '${name}' under sys config`);
|
|
22
|
+
return found;
|
|
23
|
+
}
|
|
24
|
+
let home;
|
|
25
|
+
let realHome;
|
|
26
|
+
before(() => {
|
|
27
|
+
realHome = process.env['HOME'];
|
|
28
|
+
});
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
home = mkdtempSync(join(tmpdir(), 'crtr-config-ladders-'));
|
|
31
|
+
process.env['HOME'] = home;
|
|
32
|
+
});
|
|
33
|
+
// Every test allocates its own temp HOME in beforeEach, so each must be removed
|
|
34
|
+
// in afterEach -- a single `after` would leak all but the last dir.
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
rmSync(home, { recursive: true, force: true });
|
|
37
|
+
});
|
|
38
|
+
after(() => {
|
|
39
|
+
if (realHome !== undefined)
|
|
40
|
+
process.env['HOME'] = realHome;
|
|
41
|
+
else
|
|
42
|
+
delete process.env['HOME'];
|
|
43
|
+
});
|
|
44
|
+
test('ensureScopeInitialized does NOT bake concrete modelLadders model ids into a fresh config.json', () => {
|
|
45
|
+
const root = join(home, '.crouter');
|
|
46
|
+
ensureScopeInitialized('user', root);
|
|
47
|
+
const cfgPath = join(root, 'config.json');
|
|
48
|
+
assert.equal(existsSync(cfgPath), true);
|
|
49
|
+
const raw = JSON.parse(readFileSync(cfgPath, 'utf8'));
|
|
50
|
+
// The file must exist (so `sys config path` has something to point at) but
|
|
51
|
+
// must NOT freeze a modelLadders snapshot -- that key should be absent so
|
|
52
|
+
// `readConfig` always re-derives it live from whichever build is running.
|
|
53
|
+
assert.equal(raw.modelLadders, undefined);
|
|
54
|
+
// readConfig must still default-fill a full, usable ladder despite the
|
|
55
|
+
// omission -- a config-less scope is never worse off.
|
|
56
|
+
const cfg = readConfig('user');
|
|
57
|
+
assert.equal(typeof cfg.modelLadders.anthropic.strong, 'string');
|
|
58
|
+
assert.equal(typeof cfg.modelLadders.openai.strong, 'string');
|
|
59
|
+
});
|
|
60
|
+
test('sys config set modelLadders.<provider>.<strength> persists ONLY the touched rung, not the whole materialized ladder', async () => {
|
|
61
|
+
const root = join(home, '.crouter');
|
|
62
|
+
ensureScopeInitialized('user', root);
|
|
63
|
+
await leaf('set').run({ key: 'modelLadders.openai.strong', value: 'openai-codex/gpt-5.5:high' });
|
|
64
|
+
const cfgPath = join(root, 'config.json');
|
|
65
|
+
const raw = JSON.parse(readFileSync(cfgPath, 'utf8'));
|
|
66
|
+
assert.equal(raw.modelLadders.openai.strong, 'openai-codex/gpt-5.5:high');
|
|
67
|
+
// The other three openai rungs, and the entire anthropic ladder, must stay
|
|
68
|
+
// UNSET on disk -- baking them in would freeze this scope to today's
|
|
69
|
+
// compiled defaults for rungs the user never touched, the exact staleness
|
|
70
|
+
// bug that produced the live `gpt-5.6` vs. installed-`gpt-5.5` mismatch.
|
|
71
|
+
assert.equal(raw.modelLadders.openai.ultra, undefined);
|
|
72
|
+
assert.equal(raw.modelLadders.openai.medium, undefined);
|
|
73
|
+
assert.equal(raw.modelLadders.openai.light, undefined);
|
|
74
|
+
assert.equal(raw.modelLadders.anthropic, undefined);
|
|
75
|
+
// readConfig still resolves a full ladder: the one override layered over
|
|
76
|
+
// live compiled defaults for everything else.
|
|
77
|
+
const cfg = readConfig('user');
|
|
78
|
+
assert.equal(cfg.modelLadders.openai.strong, 'openai-codex/gpt-5.5:high');
|
|
79
|
+
assert.equal(typeof cfg.modelLadders.openai.ultra, 'string');
|
|
80
|
+
assert.equal(typeof cfg.modelLadders.anthropic.strong, 'string');
|
|
81
|
+
});
|
|
82
|
+
test('an unrelated updateConfig mutation (as fresh bootstrap does) does NOT freeze modelLadders to disk', () => {
|
|
83
|
+
const root = join(home, '.crouter');
|
|
84
|
+
ensureScopeInitialized('user', root);
|
|
85
|
+
// Mimic `ensureOfficialMarketplace`'s bootstrap write: updateConfig mutating an
|
|
86
|
+
// unrelated field on a fresh scope. This must not materialize a full ladder.
|
|
87
|
+
updateConfig('user', (c) => {
|
|
88
|
+
c.marketplaces['crouter-official-marketplace'] = {
|
|
89
|
+
url: 'https://example.invalid/x.git',
|
|
90
|
+
ref: 'main',
|
|
91
|
+
installed_at: '2026-01-01T00:00:00.000Z',
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
const cfgPath = join(root, 'config.json');
|
|
95
|
+
const raw = JSON.parse(readFileSync(cfgPath, 'utf8'));
|
|
96
|
+
assert.equal(raw.modelLadders, undefined);
|
|
97
|
+
assert.ok(raw.marketplaces['crouter-official-marketplace']);
|
|
98
|
+
});
|
|
99
|
+
test('a non-ladder sys config set does NOT freeze modelLadders to disk', async () => {
|
|
100
|
+
const root = join(home, '.crouter');
|
|
101
|
+
ensureScopeInitialized('user', root);
|
|
102
|
+
await leaf('set').run({ key: 'max_panes_per_window', value: '5' });
|
|
103
|
+
const cfgPath = join(root, 'config.json');
|
|
104
|
+
const raw = JSON.parse(readFileSync(cfgPath, 'utf8'));
|
|
105
|
+
// The touched key persists; modelLadders stays absent so a later build's
|
|
106
|
+
// registry change still reaches every rung.
|
|
107
|
+
assert.equal(raw.max_panes_per_window, 5);
|
|
108
|
+
assert.equal(raw.modelLadders, undefined);
|
|
109
|
+
});
|
|
110
|
+
test('a second sys config set for a different rung does not clobber an earlier override', async () => {
|
|
111
|
+
const root = join(home, '.crouter');
|
|
112
|
+
ensureScopeInitialized('user', root);
|
|
113
|
+
await leaf('set').run({ key: 'modelLadders.openai.strong', value: 'openai-codex/gpt-5.5:high' });
|
|
114
|
+
await leaf('set').run({ key: 'modelLadders.anthropic.light', value: 'anthropic/claude-haiku-4-5:high' });
|
|
115
|
+
const cfgPath = join(root, 'config.json');
|
|
116
|
+
const raw = JSON.parse(readFileSync(cfgPath, 'utf8'));
|
|
117
|
+
assert.equal(raw.modelLadders.openai.strong, 'openai-codex/gpt-5.5:high');
|
|
118
|
+
assert.equal(raw.modelLadders.anthropic.light, 'anthropic/claude-haiku-4-5:high');
|
|
119
|
+
assert.equal(raw.modelLadders.openai.ultra, undefined);
|
|
120
|
+
assert.equal(raw.modelLadders.anthropic.strong, undefined);
|
|
121
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
2
|
-
import { readConfig,
|
|
2
|
+
import { readConfig, updateConfig, configPath as coreConfigPath } from '../../core/config.js';
|
|
3
3
|
import { usage, notFound } from '../../core/errors.js';
|
|
4
4
|
import { scopeRoot, listScopes } from '../../core/scope.js';
|
|
5
5
|
import { resolveScope } from './shared.js';
|
|
@@ -11,7 +11,6 @@ const TOP_LEVEL_KEYS = new Set([
|
|
|
11
11
|
'marketplaces',
|
|
12
12
|
'plugins',
|
|
13
13
|
'max_panes_per_window',
|
|
14
|
-
'canvasNav',
|
|
15
14
|
'modelLadders',
|
|
16
15
|
]);
|
|
17
16
|
const MODEL_PROVIDERS = new Set(['anthropic', 'openai']);
|
|
@@ -44,6 +43,9 @@ function parseConfigValue(raw) {
|
|
|
44
43
|
function setNestedValue(cfg, key, rawValue, value) {
|
|
45
44
|
const parts = key.split('.');
|
|
46
45
|
const topKey = parts[0];
|
|
46
|
+
if (topKey === 'keybindings') {
|
|
47
|
+
throw usage('keybindings are edited only through `crtr sys setup`');
|
|
48
|
+
}
|
|
47
49
|
if (!TOP_LEVEL_KEYS.has(topKey)) {
|
|
48
50
|
throw usage(`unknown config key: ${topKey} (expected: ${[...TOP_LEVEL_KEYS].join('|')})`);
|
|
49
51
|
}
|
|
@@ -69,16 +71,6 @@ function setNestedValue(cfg, key, rawValue, value) {
|
|
|
69
71
|
cfg.max_panes_per_window = Math.floor(value);
|
|
70
72
|
return;
|
|
71
73
|
}
|
|
72
|
-
if (topKey === 'canvasNav') {
|
|
73
|
-
// Only the scalar prefixKey is settable here; the prefixBinds/graphBinds
|
|
74
|
-
// record tables are edited directly in config.json (run `crtr sys config
|
|
75
|
-
// path` to locate it).
|
|
76
|
-
if (key === 'canvasNav.prefixKey') {
|
|
77
|
-
cfg.canvasNav.prefixKey = rawValue;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
throw usage(`canvasNav.${parts.slice(1).join('.') || '*'} is a record table — edit config.json directly (run \`crtr sys config path\`). Only canvasNav.prefixKey is settable via this command.`);
|
|
81
|
-
}
|
|
82
74
|
if (topKey === 'modelLadders') {
|
|
83
75
|
if (parts.length === 2 && parts[1] === 'defaultProvider') {
|
|
84
76
|
if (!isModelProvider(rawValue)) {
|
|
@@ -121,12 +113,12 @@ function setNestedValue(cfg, key, rawValue, value) {
|
|
|
121
113
|
const configGet = defineLeaf({
|
|
122
114
|
name: 'get',
|
|
123
115
|
description: 'read a config value by key',
|
|
124
|
-
whenToUse: 'you want to
|
|
116
|
+
whenToUse: 'you want to inspect one config value — including the whole user keybindings object — optionally from a specific scope. Use sys setup to edit keybindings and sys config set for supported scalar settings.',
|
|
125
117
|
help: {
|
|
126
118
|
name: 'sys config get',
|
|
127
119
|
summary: 'read a config value by dotted key',
|
|
128
120
|
params: [
|
|
129
|
-
{ kind: 'positional', name: 'key', type: 'string', required: true, constraint: 'Dotted key path. Top-level keys: auto_update, marketplaces, plugins, max_panes_per_window,
|
|
121
|
+
{ kind: 'positional', name: 'key', type: 'string', required: true, constraint: 'Dotted key path. Top-level keys: auto_update, marketplaces, plugins, max_panes_per_window, modelLadders, or the whole keybindings object. Keybindings are edited only through sys setup.' },
|
|
130
122
|
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Scope to read from. Default: user.' },
|
|
131
123
|
],
|
|
132
124
|
output: [
|
|
@@ -140,6 +132,9 @@ const configGet = defineLeaf({
|
|
|
140
132
|
run: async (input) => {
|
|
141
133
|
const key = input['key'];
|
|
142
134
|
const scope = resolveScope(input['scope']);
|
|
135
|
+
if (key.startsWith('keybindings.') || (key === 'keybindings' && scope !== 'user')) {
|
|
136
|
+
throw notFound('keybindings are available only as the whole user-scope object');
|
|
137
|
+
}
|
|
143
138
|
const cfg = readConfig(scope);
|
|
144
139
|
const value = getNestedValue(cfg, key);
|
|
145
140
|
if (value === undefined) {
|
|
@@ -151,12 +146,12 @@ const configGet = defineLeaf({
|
|
|
151
146
|
const configSet = defineLeaf({
|
|
152
147
|
name: 'set',
|
|
153
148
|
description: 'write a config value by key',
|
|
154
|
-
whenToUse: 'you want to change a
|
|
149
|
+
whenToUse: 'you want to change a supported scalar crtr setting in user or project scope. Use sys setup instead for keybindings, and sys config get to inspect values.',
|
|
155
150
|
help: {
|
|
156
151
|
name: 'sys config set',
|
|
157
152
|
summary: 'write a config value by dotted key',
|
|
158
153
|
params: [
|
|
159
|
-
{ kind: 'positional', name: 'key', type: 'string', required: true, constraint: 'Dotted key path. Supported: auto_update.crtr, auto_update.content, auto_update.interval_hours, max_panes_per_window,
|
|
154
|
+
{ kind: 'positional', name: 'key', type: 'string', required: true, constraint: 'Dotted key path. Supported: auto_update.crtr, auto_update.content, auto_update.interval_hours, max_panes_per_window, modelLadders.defaultProvider, modelLadders.<anthropic|openai>.<ultra|strong|medium|light>. Keybindings are edited only through sys setup.' },
|
|
160
155
|
{ kind: 'flag', name: 'value', type: 'string', required: true, constraint: 'value VALUE — string, required. Stored as-is if quoted; coerced to number or boolean when unambiguous.' },
|
|
161
156
|
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Scope to write to. Default: user.' },
|
|
162
157
|
],
|
|
@@ -174,9 +169,11 @@ const configSet = defineLeaf({
|
|
|
174
169
|
const scope = resolveScope(input['scope']);
|
|
175
170
|
// Flags are stringly-typed; coerce to number or boolean when unambiguous
|
|
176
171
|
const parsed = parseConfigValue(rawValue);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
172
|
+
// Mutate + persist through the central raw-partial path. `updateConfig`
|
|
173
|
+
// writes only the keys this set actually touched -- a non-ladder set never
|
|
174
|
+
// freezes a materialized `modelLadders`, and a direct
|
|
175
|
+
// `modelLadders.<provider>.<strength>` set persists only the touched rung.
|
|
176
|
+
const cfg = updateConfig(scope, (c) => setNestedValue(c, key, rawValue, parsed));
|
|
180
177
|
// Read back the written value for echo
|
|
181
178
|
const written = getNestedValue(cfg, key);
|
|
182
179
|
return { key, value: written, scope };
|
|
@@ -185,7 +182,7 @@ const configSet = defineLeaf({
|
|
|
185
182
|
const configPath = defineLeaf({
|
|
186
183
|
name: 'path',
|
|
187
184
|
description: 'print path(s) to config.json',
|
|
188
|
-
whenToUse: 'you need the absolute path to
|
|
185
|
+
whenToUse: 'you need the absolute path to a user or project config.json for inspection or backup.',
|
|
189
186
|
help: {
|
|
190
187
|
name: 'sys config path',
|
|
191
188
|
summary: 'print absolute path(s) to config.json',
|
|
@@ -225,7 +222,7 @@ const configPath = defineLeaf({
|
|
|
225
222
|
export const configBranch = defineBranch({
|
|
226
223
|
name: 'config',
|
|
227
224
|
description: 'read and write configuration',
|
|
228
|
-
whenToUse: 'inspecting
|
|
225
|
+
whenToUse: 'inspecting crtr settings, changing supported scalar values, or locating config.json. Keybinding changes belong in sys setup.',
|
|
229
226
|
help: {
|
|
230
227
|
name: 'sys config',
|
|
231
228
|
summary: 'read and write crtr configuration, including model ladders',
|
|
@@ -31,6 +31,7 @@ export interface SetupStaticInstructions {
|
|
|
31
31
|
packageLines: string[];
|
|
32
32
|
systemLines: string[];
|
|
33
33
|
exaLine: string;
|
|
34
|
+
composioLine: string;
|
|
34
35
|
footer: string;
|
|
35
36
|
}
|
|
36
37
|
export interface SetupSubmission {
|
|
@@ -39,18 +40,23 @@ export interface SetupSubmission {
|
|
|
39
40
|
systemDeps: SetupSystemInstallPlan[];
|
|
40
41
|
exaKey: string;
|
|
41
42
|
exaKeyPath: string;
|
|
43
|
+
composioKey: string;
|
|
44
|
+
composioKeyPath: string;
|
|
42
45
|
}
|
|
43
46
|
export interface SetupExecutionSummary {
|
|
44
47
|
installed: string[];
|
|
45
48
|
skipped: string[];
|
|
46
49
|
exaKeyWritten: boolean;
|
|
47
50
|
exaKeyPath: string;
|
|
51
|
+
composioKeyWritten: boolean;
|
|
52
|
+
composioKeyPath: string;
|
|
48
53
|
}
|
|
49
54
|
export interface SetupInitialState {
|
|
50
55
|
manifest: SetupPackageManifestEntry[];
|
|
51
56
|
installedPackageIds: Set<SetupPackageId>;
|
|
52
57
|
systemTools: SetupSystemToolState[];
|
|
53
58
|
exaKeyPath: string;
|
|
59
|
+
composioKeyPath: string;
|
|
54
60
|
}
|
|
55
61
|
export declare function buildSetupManifest(resolveBuiltinPackageDir?: (name: string) => string): SetupPackageManifestEntry[];
|
|
56
62
|
export declare function normalizePackageIdentity(source: string, settingsDir: string, homeDir: string): SetupPackageIdentity;
|
|
@@ -61,6 +67,7 @@ export declare function buildSystemInstallPlan(tool: SetupTool, packageManager:
|
|
|
61
67
|
export declare function buildSystemToolState(tool: SetupTool, packageManager: PackageManager, commandExists?: (command: string) => boolean): SetupSystemToolState;
|
|
62
68
|
export declare function resolveSettingsPath(homeDir?: string): string;
|
|
63
69
|
export declare function resolveExaKeyPath(homeDir?: string): string;
|
|
70
|
+
export declare function resolveComposioKeyPath(homeDir?: string): string;
|
|
64
71
|
export declare function buildSetupInitialState(opts?: {
|
|
65
72
|
manifest?: SetupPackageManifestEntry[];
|
|
66
73
|
settingsPath?: string;
|
|
@@ -73,9 +80,11 @@ export declare function buildSetupStaticInstructions(opts: {
|
|
|
73
80
|
installedPackageIds: Set<SetupPackageId>;
|
|
74
81
|
systemPlans: SetupSystemInstallPlan[];
|
|
75
82
|
exaKeyPath: string;
|
|
83
|
+
composioKeyPath: string;
|
|
76
84
|
}): SetupStaticInstructions;
|
|
77
|
-
export declare function renderSetupStaticInstructions(state: Pick<SetupInitialState, 'manifest' | 'installedPackageIds' | 'systemTools' | 'exaKeyPath'>): string;
|
|
85
|
+
export declare function renderSetupStaticInstructions(state: Pick<SetupInitialState, 'manifest' | 'installedPackageIds' | 'systemTools' | 'exaKeyPath' | 'composioKeyPath'>): string;
|
|
78
86
|
export declare function writeExaKey(exaKeyPath: string, exaKey: string): Promise<void>;
|
|
87
|
+
export declare function writeComposioKey(composioKeyPath: string, composioKey: string): Promise<void>;
|
|
79
88
|
export declare function summarizeSetupExecution(result: {
|
|
80
89
|
installedPackages: string[];
|
|
81
90
|
skippedPackages: string[];
|
|
@@ -83,6 +92,8 @@ export declare function summarizeSetupExecution(result: {
|
|
|
83
92
|
skippedSystemDeps: string[];
|
|
84
93
|
exaKeyWritten: boolean;
|
|
85
94
|
exaKeyPath: string;
|
|
95
|
+
composioKeyWritten: boolean;
|
|
96
|
+
composioKeyPath: string;
|
|
86
97
|
}): SetupExecutionSummary;
|
|
87
98
|
export declare function formatSetupExecutionSummary(summary: SetupExecutionSummary): string;
|
|
88
99
|
export declare function executeSetupSubmission(submission: SetupSubmission): Promise<SetupExecutionSummary>;
|
|
@@ -13,8 +13,8 @@ const PACKAGE_COPY = {
|
|
|
13
13
|
},
|
|
14
14
|
'mode-switch': {
|
|
15
15
|
name: 'pi-mode-switch',
|
|
16
|
-
shortDescription: 'Alt+
|
|
17
|
-
longDescription: 'Press Alt+
|
|
16
|
+
shortDescription: 'Alt+Shift+P cycles spec / plan / normal modes; injects mode-specific guidance on change.',
|
|
17
|
+
longDescription: 'Press Alt+Shift+P to rotate between three operating modes — spec (requirements gathering), plan (design before code), and normal (execution). Each switch appends mode-tailored guidance to keep the agent in the right gear.',
|
|
18
18
|
},
|
|
19
19
|
'oauth-adapter': {
|
|
20
20
|
name: 'pi-claude-oauth-adapter',
|
|
@@ -231,6 +231,9 @@ export function resolveSettingsPath(homeDir = homedir()) {
|
|
|
231
231
|
export function resolveExaKeyPath(homeDir = homedir()) {
|
|
232
232
|
return join(homeDir, '.crouter', 'exa.key');
|
|
233
233
|
}
|
|
234
|
+
export function resolveComposioKeyPath(homeDir = homedir()) {
|
|
235
|
+
return join(homeDir, '.crouter', 'composio.key');
|
|
236
|
+
}
|
|
234
237
|
export function buildSetupInitialState(opts = {}) {
|
|
235
238
|
const homeDir = opts.homeDir ?? homedir();
|
|
236
239
|
const settingsPath = opts.settingsPath ?? resolveSettingsPath(homeDir);
|
|
@@ -240,7 +243,7 @@ export function buildSetupInitialState(opts = {}) {
|
|
|
240
243
|
const commandExists = opts.commandExists ?? detectCommandOnPath;
|
|
241
244
|
const packageManager = opts.packageManager ?? detectPackageManager(process.platform, commandExists);
|
|
242
245
|
const systemTools = ['tmux', 'nvim'].map((tool) => buildSystemToolState(tool, packageManager, commandExists));
|
|
243
|
-
return { manifest, installedPackageIds, systemTools, exaKeyPath: resolveExaKeyPath(homeDir) };
|
|
246
|
+
return { manifest, installedPackageIds, systemTools, exaKeyPath: resolveExaKeyPath(homeDir), composioKeyPath: resolveComposioKeyPath(homeDir) };
|
|
244
247
|
}
|
|
245
248
|
export function buildSetupStaticInstructions(opts) {
|
|
246
249
|
return {
|
|
@@ -257,6 +260,7 @@ export function buildSetupStaticInstructions(opts) {
|
|
|
257
260
|
return `- ${plan.tool}: ${plan.hint}`;
|
|
258
261
|
}),
|
|
259
262
|
exaLine: `- Exa key: write to ${opts.exaKeyPath} or set EXA_API_KEY`,
|
|
263
|
+
composioLine: `- Composio key: write to ${opts.composioKeyPath} or set COMPOSIO_API_KEY`,
|
|
260
264
|
footer: 'Run `crtr sys setup` in a TTY to open the wizard.',
|
|
261
265
|
};
|
|
262
266
|
}
|
|
@@ -266,10 +270,13 @@ export function renderSetupStaticInstructions(state) {
|
|
|
266
270
|
installedPackageIds: state.installedPackageIds,
|
|
267
271
|
systemPlans: state.systemTools.map((tool) => tool.plan),
|
|
268
272
|
exaKeyPath: state.exaKeyPath,
|
|
273
|
+
composioKeyPath: state.composioKeyPath,
|
|
269
274
|
});
|
|
270
275
|
return [
|
|
271
276
|
instructions.headline,
|
|
272
277
|
'',
|
|
278
|
+
'Provider login: open the wizard in a TTY and use the Login tab (same flow as /login) to authenticate a model provider.',
|
|
279
|
+
'',
|
|
273
280
|
'Packages:',
|
|
274
281
|
...instructions.packageLines,
|
|
275
282
|
'',
|
|
@@ -277,6 +284,7 @@ export function renderSetupStaticInstructions(state) {
|
|
|
277
284
|
...instructions.systemLines,
|
|
278
285
|
'',
|
|
279
286
|
instructions.exaLine,
|
|
287
|
+
instructions.composioLine,
|
|
280
288
|
instructions.footer,
|
|
281
289
|
].join('\n') + '\n';
|
|
282
290
|
}
|
|
@@ -297,12 +305,19 @@ export async function writeExaKey(exaKeyPath, exaKey) {
|
|
|
297
305
|
await writeFile(exaKeyPath, `${exaKey}\n`, 'utf8');
|
|
298
306
|
await chmod(exaKeyPath, 0o600).catch(() => undefined);
|
|
299
307
|
}
|
|
308
|
+
export async function writeComposioKey(composioKeyPath, composioKey) {
|
|
309
|
+
await mkdir(dirname(composioKeyPath), { recursive: true });
|
|
310
|
+
await writeFile(composioKeyPath, `${composioKey}\n`, 'utf8');
|
|
311
|
+
await chmod(composioKeyPath, 0o600).catch(() => undefined);
|
|
312
|
+
}
|
|
300
313
|
export function summarizeSetupExecution(result) {
|
|
301
314
|
return {
|
|
302
315
|
installed: [...result.installedPackages, ...result.installedSystemDeps],
|
|
303
316
|
skipped: [...result.skippedPackages, ...result.skippedSystemDeps],
|
|
304
317
|
exaKeyWritten: result.exaKeyWritten,
|
|
305
318
|
exaKeyPath: result.exaKeyPath,
|
|
319
|
+
composioKeyWritten: result.composioKeyWritten,
|
|
320
|
+
composioKeyPath: result.composioKeyPath,
|
|
306
321
|
};
|
|
307
322
|
}
|
|
308
323
|
export function formatSetupExecutionSummary(summary) {
|
|
@@ -311,6 +326,7 @@ export function formatSetupExecutionSummary(summary) {
|
|
|
311
326
|
summary.installed.length > 0 ? `installed: ${summary.installed.join(', ')}` : 'installed: none',
|
|
312
327
|
summary.skipped.length > 0 ? `skipped: ${summary.skipped.join(', ')}` : 'skipped: none',
|
|
313
328
|
summary.exaKeyWritten ? `exa key written: ${summary.exaKeyPath}` : 'exa key written: no',
|
|
329
|
+
summary.composioKeyWritten ? `composio key written: ${summary.composioKeyPath}` : 'composio key written: no',
|
|
314
330
|
].join('\n') + '\n';
|
|
315
331
|
}
|
|
316
332
|
export async function executeSetupSubmission(submission) {
|
|
@@ -342,6 +358,10 @@ export async function executeSetupSubmission(submission) {
|
|
|
342
358
|
if (exaKeyWritten) {
|
|
343
359
|
await writeExaKey(submission.exaKeyPath, submission.exaKey);
|
|
344
360
|
}
|
|
361
|
+
const composioKeyWritten = submission.composioKey.trim() !== '';
|
|
362
|
+
if (composioKeyWritten) {
|
|
363
|
+
await writeComposioKey(submission.composioKeyPath, submission.composioKey);
|
|
364
|
+
}
|
|
345
365
|
return summarizeSetupExecution({
|
|
346
366
|
installedPackages,
|
|
347
367
|
skippedPackages,
|
|
@@ -349,5 +369,7 @@ export async function executeSetupSubmission(submission) {
|
|
|
349
369
|
skippedSystemDeps,
|
|
350
370
|
exaKeyWritten,
|
|
351
371
|
exaKeyPath: submission.exaKeyPath,
|
|
372
|
+
composioKeyWritten,
|
|
373
|
+
composioKeyPath: submission.composioKeyPath,
|
|
352
374
|
});
|
|
353
375
|
}
|