@ctrl-spc/cs 0.7.0 → 0.7.2
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/codex-home.js +72 -7
- package/dist/config.js +17 -0
- package/dist/index.js +93 -12
- package/dist/mcp.js +15 -6
- package/dist/panel3/answer.js +12 -12
- package/dist/panel3/checkout.js +522 -2
- package/dist/panel3/cli.js +49 -62
- package/dist/panel3/client.js +53 -16
- package/dist/panel3/presence.js +1 -1
- package/dist/panel3/prompt.js +473 -62
- package/dist/panel3/run.js +1206 -111
- package/dist/panel3/say.js +10 -10
- package/dist/panel3/session.js +128 -0
- package/dist/panel3/show.js +197 -23
- package/dist/panel3/spawn.js +85 -20
- package/dist/panel3/tools.js +677 -51
- package/dist/presence.js +8 -0
- package/dist/skills.js +165 -0
- package/dist/workflows.js +68 -0
- package/package.json +2 -3
package/dist/panel3/cli.js
CHANGED
|
@@ -1,83 +1,70 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* ═══ AGENT PANEL v3: the
|
|
3
|
+
* ═══ AGENT PANEL v3: the panel's commands, and the harness bin that runs them. ═══
|
|
4
4
|
*
|
|
5
5
|
* THIS FILE BELONGS TO AGENT PANEL v3. Nothing outside `src/panel3/` may
|
|
6
|
-
* import it.
|
|
6
|
+
* import it, but for the one mount `cli-v2/src/index.ts` makes on
|
|
7
|
+
* `panelCommand`, named in `test/panel3-isolation.contract.test.mjs`.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* ═══ THE PERSON TYPES `cs`, AND ONLY `cs`. ═══ Lane's ruling (2026-08-24):
|
|
10
|
+
* nothing the product names may carry a version the person never knew about,
|
|
11
|
+
* and there is one CLI to launch and sign into. So `say`, `answer` and `show`
|
|
12
|
+
* are `cs` subcommands, reached through `panelCommand` below, and the `cs3` bin
|
|
13
|
+
* that used to publish them is gone. The router stays HERE rather than moving
|
|
14
|
+
* into `index.ts` so that v3 is still one directory to delete: `index.ts` holds
|
|
15
|
+
* three cases and a single import, not the panel's argument handling.
|
|
12
16
|
*
|
|
13
|
-
* ═══ AND
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* one `run()` in `run.ts` with two callers
|
|
21
|
-
*
|
|
22
|
-
* `test/panel3-acceptance.mjs` and the three walk scripts stand a daemon up
|
|
23
|
-
* against their own isolated account and their own working copy without touching
|
|
24
|
-
* the installed CLI's `session.json` or its presence. Deleting it would mean
|
|
25
|
-
* rebuilding that isolation inside `cs start`, which is a launch path the tests
|
|
26
|
-
* would then own. DO NOT PUT IT BACK IN THE HELP TEXT.
|
|
17
|
+
* ═══ AND `run` IS STILL NOT SOMETHING A PERSON TYPES. ═══ `cs start` runs the
|
|
18
|
+
* panel's poll loop through `startPanel`, so that is the only launch the product
|
|
19
|
+
* names. `run` is reachable only by running this file directly, which is what
|
|
20
|
+
* `test/panel3-acceptance.mjs` and the walk scripts do. It signs in from
|
|
21
|
+
* `CTRL_SPC_V3_EMAIL` / `CTRL_SPC_V3_PASSWORD`, which is what lets them stand a
|
|
22
|
+
* daemon up against their own isolated account and their own working copy
|
|
23
|
+
* without touching the installed CLI's `session.json` or its presence. There is
|
|
24
|
+
* one `run()` in `run.ts` with two callers, not two copies of the loop. DO NOT
|
|
25
|
+
* PUT IT IN `cs`.
|
|
27
26
|
*
|
|
28
27
|
* ═══ A FAILURE EXITS NON-ZERO AND SAYS WHY, ALWAYS. ═══ Every read in `show`
|
|
29
|
-
* and every write in `say` either returns its rows or throws, and
|
|
30
|
-
* only
|
|
31
|
-
* command whose whole job is observation is worthless
|
|
32
|
-
* mean either "nothing is there" or "the read
|
|
33
|
-
* zero without writing would be an
|
|
28
|
+
* and every write in `say` either returns its rows or throws, and the two
|
|
29
|
+
* handlers below are the only ones. Nothing here converts a failure into an
|
|
30
|
+
* empty result, because a command whose whole job is observation is worthless
|
|
31
|
+
* the first time silence can mean either "nothing is there" or "the read
|
|
32
|
+
* failed", and a `say` that exited zero without writing would be an
|
|
33
|
+
* acknowledgement of nothing.
|
|
34
34
|
*/
|
|
35
|
+
import { realpathSync } from 'node:fs';
|
|
36
|
+
import { fileURLToPath } from 'node:url';
|
|
35
37
|
import { answer } from './answer.js';
|
|
36
38
|
import { run } from './run.js';
|
|
37
39
|
import { say } from './say.js';
|
|
38
40
|
import { show } from './show.js';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
cs3 show <id> One card in full, or one run with its brief and report
|
|
47
|
-
cs3 help Show this help
|
|
48
|
-
|
|
49
|
-
This machine answers cards when \`cs start\` is running. These commands are the
|
|
50
|
-
terminal's view of the same record.
|
|
51
|
-
|
|
52
|
-
Acts as CTRL_SPC_V3_EMAIL / CTRL_SPC_V3_PASSWORD, against the hosted project
|
|
53
|
-
by default. Point it at a local stack with:
|
|
54
|
-
CTRL_SPC_SUPABASE_URL=http://127.0.0.1:54321
|
|
55
|
-
CTRL_SPC_SUPABASE_KEY=<the local publishable key from \`supabase status\`>
|
|
56
|
-
`;
|
|
57
|
-
async function main() {
|
|
58
|
-
const command = process.argv[2];
|
|
41
|
+
/**
|
|
42
|
+
* One panel command, given the whole tail of `process.argv` from the command
|
|
43
|
+
* word on. `cs` routes `say`, `answer` and `show` here; running this file
|
|
44
|
+
* directly reaches `run` as well.
|
|
45
|
+
*/
|
|
46
|
+
export async function panelCommand(argv) {
|
|
47
|
+
const command = argv[0];
|
|
59
48
|
switch (command) {
|
|
60
49
|
case 'say':
|
|
61
|
-
return say(
|
|
50
|
+
return say(argv.slice(1));
|
|
62
51
|
case 'answer':
|
|
63
|
-
return answer(
|
|
52
|
+
return answer(argv.slice(1));
|
|
64
53
|
case 'run':
|
|
65
|
-
return run(
|
|
54
|
+
return run(argv.slice(1));
|
|
66
55
|
case 'show':
|
|
67
|
-
return show(
|
|
68
|
-
case undefined:
|
|
69
|
-
case 'help':
|
|
70
|
-
case '--help':
|
|
71
|
-
case '-h':
|
|
72
|
-
console.log(HELP);
|
|
73
|
-
return;
|
|
56
|
+
return show(argv[1]);
|
|
74
57
|
default:
|
|
75
|
-
|
|
76
|
-
console.log(HELP);
|
|
77
|
-
process.exitCode = 1;
|
|
58
|
+
throw new Error(`unknown command: ${command ?? '(none)'}`);
|
|
78
59
|
}
|
|
79
60
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
61
|
+
/* THE HARNESS ENTRY, AND IT MUST NOT FIRE WHEN `cs` IMPORTS `panelCommand`.
|
|
62
|
+
Both sides go through `realpathSync` because the installed CLI is reached
|
|
63
|
+
through a symlinked bin, and comparing the raw argument to the module path
|
|
64
|
+
would make this file two different files depending on how it was reached. */
|
|
65
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
|
|
66
|
+
panelCommand(process.argv.slice(2)).catch((err) => {
|
|
67
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
});
|
|
70
|
+
}
|
package/dist/panel3/client.js
CHANGED
|
@@ -16,14 +16,23 @@
|
|
|
16
16
|
* copying it a third time.
|
|
17
17
|
*
|
|
18
18
|
* ---------------------------------------------------------------------------
|
|
19
|
-
* WHY
|
|
19
|
+
* WHY A SESSION OF ITS OWN, AND WHY THE STORED ONE IS READ THE WAY IT IS.
|
|
20
20
|
*
|
|
21
21
|
* The installed CLI keeps one `session.json` on this machine and its long-lived
|
|
22
22
|
* daemon holds the access token that file names. A second process that reads it
|
|
23
|
-
* and lets the client
|
|
24
|
-
* and the daemon's token stops working. So
|
|
25
|
-
* in memory, and never touches the file:
|
|
26
|
-
* client has any storage to write a rotated
|
|
23
|
+
* and lets the client REFRESH writes a ROTATED token back over the same file,
|
|
24
|
+
* and the daemon's token stops working. So when credentials are given, v3 takes
|
|
25
|
+
* a fresh session of its own, in memory, and never touches the file:
|
|
26
|
+
* `persistSession: false`, and no v3 client has any storage to write a rotated
|
|
27
|
+
* token into.
|
|
28
|
+
*
|
|
29
|
+
* ═══ AND WITH NO CREDENTIALS IT READS THAT FILE, BECAUSE THE PERSON TYPES
|
|
30
|
+
* `cs`. ═══ `say`, `answer` and `show` are `cs` subcommands, and a person
|
|
31
|
+
* who ran `cs login` has no v3 environment set and must not be asked to sign in
|
|
32
|
+
* twice. `storedSessionClient` below is that read, in the one shape that cannot
|
|
33
|
+
* rotate anything: the access token on a header, no auth session, no refresh
|
|
34
|
+
* token, nothing written back. The hazard above is about REFRESHING against
|
|
35
|
+
* that file, not about reading it.
|
|
27
36
|
*
|
|
28
37
|
* ═══ THAT IS NOW TRUE OF THE COMMANDS AND NOT OF THE PANEL, AND THE DIFFERENCE
|
|
29
38
|
* IS THE WORD "PROCESS". ═══ recovery-1 Slice 4: `cs start` runs the poll
|
|
@@ -35,9 +44,9 @@
|
|
|
35
44
|
* token. `signedInClient` must never be called from that path, because taking a
|
|
36
45
|
* session of its own there is what would recreate the race indoors.
|
|
37
46
|
*
|
|
38
|
-
* So this function now serves `show`, `say`, `answer`, and
|
|
39
|
-
*
|
|
40
|
-
*
|
|
47
|
+
* So this function now serves `show`, `say`, `answer`, and the acceptance
|
|
48
|
+
* harness's isolated `run` entry — every one of them a process that is NOT the
|
|
49
|
+
* installed daemon, which is exactly the set the paragraph above describes.
|
|
41
50
|
*
|
|
42
51
|
* ---------------------------------------------------------------------------
|
|
43
52
|
* ═══ AND THE DAEMON'S CLIENT REFRESHES, WHILE A ONE-SHOT COMMAND'S DOES NOT.
|
|
@@ -45,8 +54,8 @@
|
|
|
45
54
|
*
|
|
46
55
|
* `autoRefreshToken` was off for every v3 client, which is right for `show`,
|
|
47
56
|
* `say` and `answer` — each signs in, does one thing and exits, long inside the
|
|
48
|
-
* access token's lifetime — and WRONG for `
|
|
49
|
-
* as long as the daemon lives and hands that same client to every tool call an
|
|
57
|
+
* access token's lifetime — and WRONG for the harness's `run`, which holds one
|
|
58
|
+
* client for as long as the daemon lives and hands that same client to every tool call an
|
|
50
59
|
* agent makes. Past the token's TTL every poll and every in-flight tool call
|
|
51
60
|
* would start failing, and the card would sit working with a daemon that could
|
|
52
61
|
* no longer read or write anything.
|
|
@@ -75,8 +84,9 @@
|
|
|
75
84
|
* published (`"files": ["dist"]`, `"access": "public"`), and `dist/` is
|
|
76
85
|
* committed, so a literal password here is one `npm publish` away from the
|
|
77
86
|
* public registry — a harm about DISTRIBUTION that the loopback guard below,
|
|
78
|
-
* which is about where a request is SENT, does nothing to prevent. So
|
|
79
|
-
*
|
|
87
|
+
* which is about where a request is SENT, does nothing to prevent. So no
|
|
88
|
+
* password is ever defaulted: unset falls through to the stored `cs login`
|
|
89
|
+
* session, and with neither the command refuses and says what to do.
|
|
80
90
|
*
|
|
81
91
|
* ---------------------------------------------------------------------------
|
|
82
92
|
* ═══ THE LOOPBACK-ONLY GUARD IS GONE, AND THIS IS THE EDIT IT ASKED FOR. ═══
|
|
@@ -107,6 +117,7 @@
|
|
|
107
117
|
*/
|
|
108
118
|
import { createClient } from '@supabase/supabase-js';
|
|
109
119
|
import { SUPABASE_URL, SUPABASE_KEY } from '../env.js';
|
|
120
|
+
import { readSession } from '../config.js';
|
|
110
121
|
/**
|
|
111
122
|
* How a v3 client holds its session, for the two lifetimes v3 has.
|
|
112
123
|
*
|
|
@@ -122,6 +133,34 @@ export const sessionOptions = (living) => ({
|
|
|
122
133
|
the token's lifetime, and the daemon does not. */
|
|
123
134
|
autoRefreshToken: living,
|
|
124
135
|
});
|
|
136
|
+
/**
|
|
137
|
+
* ═══ THE PERSON SIGNED IN ONCE, WITH `cs login`, AND `cs say` MUST NOT ASK
|
|
138
|
+
* AGAIN. ═══
|
|
139
|
+
*
|
|
140
|
+
* `say`, `answer` and `show` are `cs` subcommands, so the person who ran
|
|
141
|
+
* `cs login` types them with no environment set at all. This is that session,
|
|
142
|
+
* and it is read in the one shape that CANNOT rotate the token the daemon is
|
|
143
|
+
* holding: the stored access token goes on the Authorization header, there is no
|
|
144
|
+
* auth session on the client, no refresh token is handed over, and nothing is
|
|
145
|
+
* ever written back to `session.json`. A rotation here is exactly the failure
|
|
146
|
+
* the header above describes, and the header cannot happen if no client in this
|
|
147
|
+
* process ever holds a refresh token.
|
|
148
|
+
*
|
|
149
|
+
* An expired access token surfaces as the request's own 401 through
|
|
150
|
+
* `returned()`, which is the truthful failure: `cs start` refreshes the file
|
|
151
|
+
* this reads, so a person whose machine is online has a live token.
|
|
152
|
+
*/
|
|
153
|
+
function storedSessionClient() {
|
|
154
|
+
const stored = readSession();
|
|
155
|
+
if (!stored?.access_token) {
|
|
156
|
+
throw new Error('not signed in. Run `cs login`, or set CTRL_SPC_V3_EMAIL and CTRL_SPC_V3_PASSWORD to the '
|
|
157
|
+
+ `account this command should act as at ${SUPABASE_URL}`);
|
|
158
|
+
}
|
|
159
|
+
return createClient(SUPABASE_URL, SUPABASE_KEY, {
|
|
160
|
+
auth: sessionOptions(false),
|
|
161
|
+
global: { headers: { Authorization: `Bearer ${stored.access_token}` } },
|
|
162
|
+
});
|
|
163
|
+
}
|
|
125
164
|
/**
|
|
126
165
|
* @param living whether this client outlives its access token. The daemon's
|
|
127
166
|
* does; every one-shot command's does not.
|
|
@@ -129,10 +168,8 @@ export const sessionOptions = (living) => ({
|
|
|
129
168
|
export async function signedInClient(living = false) {
|
|
130
169
|
const email = process.env.CTRL_SPC_V3_EMAIL;
|
|
131
170
|
const password = process.env.CTRL_SPC_V3_PASSWORD;
|
|
132
|
-
if (!email || !password)
|
|
133
|
-
|
|
134
|
-
+ `should act as at ${SUPABASE_URL}`);
|
|
135
|
-
}
|
|
171
|
+
if (!email || !password)
|
|
172
|
+
return storedSessionClient();
|
|
136
173
|
const client = createClient(SUPABASE_URL, SUPABASE_KEY, { auth: sessionOptions(living) });
|
|
137
174
|
const { data, error } = await client.auth.signInWithPassword({ email, password });
|
|
138
175
|
// Truthfully, and naming both halves of what was attempted: a wrong password
|
package/dist/panel3/presence.js
CHANGED
|
@@ -9,7 +9,7 @@ import { returned } from './client.js';
|
|
|
9
9
|
export const LISTENING_WINDOW_MS = 15_000;
|
|
10
10
|
/** Is this machine still listening?
|
|
11
11
|
*
|
|
12
|
-
* THE ONE PREDICATE, shared by `
|
|
12
|
+
* THE ONE PREDICATE, shared by `cs show` and the panel, so the two screens
|
|
13
13
|
* cannot disagree about whether anything can pick a card up.
|
|
14
14
|
*
|
|
15
15
|
* ═══ IT TAKES THE ROW, NOT A TIMESTAMP, BECAUSE FRESHNESS IS NO LONGER THE
|