ccdeck 1.43.0 → 1.45.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.
@@ -0,0 +1,156 @@
1
+ // The command line, parsed: everything the deck recognises, and everything it
2
+ // does not.
3
+ //
4
+ // Its own module rather than a function inside bin/deck.js, for one reason —
5
+ // importing bin/deck.js RUNS a deck. That file installs hooks, binds a port and
6
+ // opens a browser at module scope, so there was no way to ask what `--prot`
7
+ // parses to without starting a server to find out. Nothing here does any of
8
+ // that: strings in, a plain object out, no I/O, no process, no terminal.
9
+ //
10
+ // bin/deck.js is the only caller. The parser is the whole of the module's
11
+ // surface; the tables it matches against stay inside it.
12
+
13
+ /**
14
+ * Is this token a flag rather than somebody's value?
15
+ *
16
+ * A leading `-` and nothing else, because the alternatives are worse. Matching
17
+ * the known flag list would refuse `--workspace --prot` — a typo eating the next
18
+ * token is the same accident as a real flag eating it, and the one shape that
19
+ * must be caught is the one nobody spelled right. Refusing every value that
20
+ * begins with `-` would refuse `--port -1`, which is a number the user meant and
21
+ * which deserves the port error below rather than a "missing value" one.
22
+ *
23
+ * So: a leading `-`, except a plain negative number. The negative-number carve
24
+ * is the only exception, and it is small on purpose.
25
+ *
26
+ * WHAT THIS DOES ON EACH PLATFORM. Nothing, is the intent, to any real path.
27
+ * POSIX absolute (`/srv/proj`), POSIX relative (`./sub`, `sub`), `~/proj`, a
28
+ * Windows drive letter (`C:\Users\u\proj`, `c:/users/u/proj`), a UNC share
29
+ * (`\\srv\share\proj`), a Windows long path (`\\?\C:\proj`) and a bare
30
+ * `events.jsonl` all begin with something other than `-`, so all of them are
31
+ * values. A drive letter is not a flag on any platform and is never read as one:
32
+ * `C:` starts with `C`. What IS refused is a directory whose name really begins
33
+ * with a dash, which the user can still pass as `./-weird` — a two-character
34
+ * price for catching `--workspace $UNSET --no-persist`.
35
+ */
36
+ export function looksLikeFlag(token) {
37
+ return typeof token === "string" && token.startsWith("-") && !/^-\d+(?:\.\d+)?$/.test(token);
38
+ }
39
+
40
+ /**
41
+ * Is this a port the deck could bind? Digits only, inside the range Node's
42
+ * `listen` accepts.
43
+ *
44
+ * `Number()` alone is far too willing: it takes `" 4500 "`, `0x10e4`, `1e3` and
45
+ * `Infinity`, and turns everything else into the `NaN` that used to reach
46
+ * `listen` and die there. The whole point of asking here is to answer BEFORE the
47
+ * deck has installed hooks and probed for tools, and to answer about the string
48
+ * the user actually typed. See bin/deck.js, which prints the flag and the value
49
+ * back at them.
50
+ */
51
+ export function isPortValue(raw) {
52
+ if (typeof raw !== "string" && typeof raw !== "number") return false;
53
+ const s = String(raw).trim();
54
+ if (!/^\d+$/.test(s)) return false;
55
+ const n = Number(s);
56
+ return n >= 0 && n <= 65535;
57
+ }
58
+
59
+ /**
60
+ * Parse `process.argv.slice(2)`.
61
+ *
62
+ * Returns the flags that were set, plus two lists that are always present and
63
+ * always arrays:
64
+ *
65
+ * `unknown` — every token the loop did not recognise, in the order it met
66
+ * them. That list is the point of this module: the loop used
67
+ * to have no `else`, so `ccdeck --prot 4500` booted on 4317 and
68
+ * said nothing, and a typo was indistinguishable from a flag
69
+ * that worked.
70
+ * `incomplete` — every value-taking flag that was given no value it could
71
+ * use, as `{ flag, expects }`, `flag` spelled the way the user
72
+ * spelled it. bin/deck.js prints one row per entry.
73
+ *
74
+ * THE THREE FLAGS THAT TAKE A VALUE (`--port`/`-p`, `--workspace`, `--history`)
75
+ * used to consume the next token with `args[++i]` whatever it was. That is right
76
+ * for `--port 4500` — the value must never be re-examined as a token of its own,
77
+ * or every correct command line would report its own port as an unknown option,
78
+ * and a warning that fires on correct input is a warning everybody learns to
79
+ * ignore. It was wrong for a value that is itself a flag (#697): `ccdeck
80
+ * --workspace $PROJ --no-persist` with `PROJ` unset is, after word splitting,
81
+ * `ccdeck --workspace --no-persist`. The deck scoped itself to a directory
82
+ * called `--no-persist`, wrote to the shared events log anyway, and reported
83
+ * neither — `unknown` stayed empty, because the token that would have gone in it
84
+ * had been eaten.
85
+ *
86
+ * So the consume is conditional now, and it refuses three shapes:
87
+ *
88
+ * * the next token looks like a flag — NOT consumed, so the loop meets it on
89
+ * the next pass and it is parsed as the flag it is, or reported as unknown.
90
+ * That is also what fixes the supervisor case in bin/agent-dag.js: a
91
+ * respawn appends `--port <bound>` to the user's argv, and an argv ending in
92
+ * a bare `--workspace` used to eat the `--port` and drop the deck back on
93
+ * 4317, out from under the tab the user was looking at.
94
+ * * there is no next token at all — the trailing `--workspace`, which used to
95
+ * set `undefined` and mean "the default", silently.
96
+ * * the next token is empty or blank — consumed (it was quoted, so it was
97
+ * meant as the value) but not used. `--workspace ""` is a variable that did
98
+ * not expand, not a request for machine-wide capture, and answering it with
99
+ * the widest possible scope is the one answer that cannot be recovered from.
100
+ *
101
+ * In all three the flag is left UNSET, so the deck falls back to its documented
102
+ * default, and the flag is named in `incomplete` so the fallback is said out
103
+ * loud rather than discovered later.
104
+ *
105
+ * A bare word is `unknown` too, and deliberately: the deck takes no positional
106
+ * arguments at all, so `ccdeck ~/proj` is the same mistake as `ccdeck --workpace
107
+ * ~/proj` — something the deck read and then did nothing with. It is also how
108
+ * an unquoted path with a space in it becomes visible, which is a failure this
109
+ * repo already knew about and could not previously report: `--workspace
110
+ * C:\Users\John Smith\proj` reaches the parser as two arguments, and the second
111
+ * one used to be dropped in silence (see launchNpx in bin/agent-dag.js).
112
+ */
113
+ export function parseArgs(args) {
114
+ const out = { unknown: [], incomplete: [] };
115
+ for (let i = 0; i < args.length; i++) {
116
+ const a = args[i];
117
+ // The value of the flag just matched, or `undefined` when there is nothing
118
+ // usable there. Closes over `i` so it can decline to advance it: not
119
+ // consuming is what hands the token back to the loop.
120
+ const value = (expects) => {
121
+ const next = args[i + 1];
122
+ if (next === undefined || looksLikeFlag(next)) {
123
+ out.incomplete.push({ flag: a, expects });
124
+ return undefined;
125
+ }
126
+ i++;
127
+ if (String(next).trim() === "") {
128
+ out.incomplete.push({ flag: a, expects });
129
+ return undefined;
130
+ }
131
+ return next;
132
+ };
133
+ // Assigned only when there is a value, so an unusable one leaves the key
134
+ // absent and the deck on its default — see the doc comment.
135
+ const set = (key, expects) => {
136
+ const v = value(expects);
137
+ if (v !== undefined) out[key] = v;
138
+ };
139
+ if (a === "-h" || a === "--help") out.help = true;
140
+ else if (a === "-v" || a === "--version") out.version = true;
141
+ else if (a === "-p" || a === "--port") set("port", "a port number");
142
+ else if (a === "--no-open") out.noOpen = true;
143
+ else if (a === "--uninstall") out.uninstall = true;
144
+ else if (a === "--workspace") set("workspace", "a path");
145
+ else if (a === "--scope") out.scope = true;
146
+ else if (a === "--all") out.all = true; // legacy no-op (now default)
147
+ else if (a === "--no-persist") out.noPersist = true;
148
+ else if (a === "--history") set("history", "a path");
149
+ else if (a === "--codex") out.codex = true;
150
+ else if (a === "--no-codex") out.noCodex = true;
151
+ else if (a === "--claude") out.claude = true;
152
+ else if (a === "--no-claude") out.noClaude = true;
153
+ else out.unknown.push(a);
154
+ }
155
+ return out;
156
+ }
@@ -0,0 +1,16 @@
1
+ // The name the server prints, in one place — the console half of what
2
+ // src/web/brand.ts holds for the browser and src/server/term.mjs draws as the
3
+ // banner wordmark. Every `PRODUCT:` prefix below is a line a user reads when
4
+ // something has already gone wrong, which is the worst moment to introduce
5
+ // yourself by a name that appears nowhere else they have seen.
6
+ //
7
+ // Display name only. The package published to npm is still `agents-deck`, so
8
+ // self-update.mjs's `name` defaults stay as they are — that string is what the
9
+ // registry is queried for and what `npm i -g` installs, and an install that
10
+ // names the product instead of the package installs nothing. Same for the
11
+ // marker files under ~/.agents-deck, the event log under ~/.claude/agent-dag,
12
+ // the AGENTS_DECK_* variables and the `user-agent` headers: identifiers outlive
13
+ // a rename, and moving one orphans a running deck from its own state.
14
+
15
+ /** The prefix on every line the deck writes to a console or a log. */
16
+ export const PRODUCT = "ccdeck";