worktrust 0.9.7 → 0.9.9
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 +2 -0
- package/package.json +1 -1
- package/preserve-lines.mjs +18 -1
- package/stretch-evidence.mjs +115 -5
- package/worktrust.mjs +1 -1
package/README.md
CHANGED
|
@@ -184,6 +184,8 @@ nothing it does not:
|
|
|
184
184
|
| `reach` | the work's reach and its plan: commands on infrastructure (terraform, kubectl, helm, docker), data (psql, sqlite, bq, dbt) and cloud (aws, gcloud, az, supabase, vercel); plan or roadmap documents written and whether one came before the first code change; reviews approved, sent back or commented and issues opened or closed from the terminal; counts and one flag, never a command, a file name or a path (0.9.4) |
|
|
185
185
|
| `hygiene.secrets` | counts a secret touched only when a file that holds one (a .env, a private key, credentials.json, .aws/credentials) is shown or copied (cat, head, tail, base64, cp); a search for a word (grep, sed, awk) is no read (0.9.5) |
|
|
186
186
|
| `hygiene.secrets` | counts a secret shown only when a reader (cat, head, tail, base64, cp) takes the file as its own argument; a command that uses a .env and reads its output, and a template (.env.example), are no secret shown (0.9.6) |
|
|
187
|
+
| `outcomes` | outcomes of the steps: per kind of check whether its first run passed, delivery attempts that failed, a model change and a new plan after a failure each followed by a passing check; and a check whose output goes through a pipe is read from its output's words (an error fails, a pass passes, a silent type check or lint passes, anything else is unconfirmed), never from the pipe's exit code (0.9.8) |
|
|
188
|
+
| `retrieval` | retrieval and grounding: searches (grep, rg, find, Grep, Glob, a web search, an MCP search tool), the same search again, a search followed by a read or a change (found) or by another search (again), searches before the first change and right after a failure, and deliveries with docs, tests or a search since the last change (grounded); counts only, nothing searched is kept (0.9.9) |
|
|
187
189
|
| `collector_version` | the CLI version that measured it |
|
|
188
190
|
| `seq`, `prev`, `hash` | the line's place, the hash of the line before it, and its own hash |
|
|
189
191
|
|
package/package.json
CHANGED
package/preserve-lines.mjs
CHANGED
|
@@ -140,6 +140,18 @@ export function archiveLine(entry, behaviour = null, { ids = {}, collector, salt
|
|
|
140
140
|
if (typeof entry.reach.plan_first === "boolean" && reach.plan_docs) reach.plan_first = entry.reach.plan_first;
|
|
141
141
|
if (Object.keys(reach).length > 0) line.reach = reach;
|
|
142
142
|
}
|
|
143
|
+
// 0.9.8: outcomes of the steps; whole counts, and first-pass per known kind of check.
|
|
144
|
+
if (entry.outcomes && typeof entry.outcomes === "object") {
|
|
145
|
+
const outcomes = Object.fromEntries(OUTCOME_KEYS.filter((key) => whole(entry.outcomes[key])).map((key) => [key, entry.outcomes[key]]));
|
|
146
|
+
const first = entry.outcomes.first_pass && typeof entry.outcomes.first_pass === "object" ? Object.fromEntries(CHECK_KINDS_KEPT.filter((kind) => typeof entry.outcomes.first_pass[kind] === "boolean").map((kind) => [kind, entry.outcomes.first_pass[kind]])) : {};
|
|
147
|
+
if (Object.keys(first).length > 0) outcomes.first_pass = first;
|
|
148
|
+
if (Object.keys(outcomes).length > 0) line.outcomes = outcomes;
|
|
149
|
+
}
|
|
150
|
+
// 0.9.9: retrieval and grounding; whole counts only.
|
|
151
|
+
if (entry.retrieval && typeof entry.retrieval === "object") {
|
|
152
|
+
const retrieval = Object.fromEntries(RETRIEVAL_KEYS.filter((key) => whole(entry.retrieval[key])).map((key) => [key, entry.retrieval[key]]));
|
|
153
|
+
if (Object.keys(retrieval).length > 0) line.retrieval = retrieval;
|
|
154
|
+
}
|
|
143
155
|
const risk = pick(entry.risk, ["proposed", "refused", "run"]);
|
|
144
156
|
if (risk && risk.proposed > 0 && risk.refused + risk.run <= risk.proposed) line.risk = risk;
|
|
145
157
|
const keyed = (record, keys) => { const kept = Object.entries(record && typeof record === "object" ? record : {}).filter(([key, n]) => keys.includes(key) && whole(n) && n > 0).sort(); return kept.length ? Object.fromEntries(kept) : null; };
|
|
@@ -199,6 +211,11 @@ export function archiveEntries(dir, ownKey) {
|
|
|
199
211
|
/** The furthest step a stretch reached on the computer (0.8.9). */
|
|
200
212
|
const STAGES = ["attempted", "verified", "committed", "pushed", "pr", "deployed"];
|
|
201
213
|
/** How fast a stretch moved (0.9.0). */
|
|
214
|
+
/** Retrieval and grounding (0.9.9). */
|
|
215
|
+
const RETRIEVAL_KEYS = ["searches", "repeated", "found", "again", "before_change", "after_failure", "deliveries", "grounded"];
|
|
216
|
+
/** Outcomes of the steps (0.9.8). */
|
|
217
|
+
const OUTCOME_KEYS = ["deliveries_failed", "escalations_helped", "replans_helped"];
|
|
218
|
+
const CHECK_KINDS_KEPT = ["test", "typecheck", "lint", "build", "gate", "ci", "security"];
|
|
202
219
|
/** The work's reach and its plan (0.9.4). */
|
|
203
220
|
const REACH_KEYS = ["infra", "data", "cloud", "plan_docs", "reviews_approved", "reviews_changes", "reviews_commented", "issues_opened", "issues_closed"];
|
|
204
221
|
/** Evidence read and adaptation (0.9.3). */
|
|
@@ -209,7 +226,7 @@ const ROUTE_KEYS = ["branches", "pushes_to_main", "commits_large", "fix_followup
|
|
|
209
226
|
/** Risk and hygiene (0.9.1). */
|
|
210
227
|
const HYGIENE_KEYS = ["rollbacks", "privileged", "bypasses", "secrets", "destructive", "destructive_checked", "guard_retried", "guard_changed", "guard_stopped"];
|
|
211
228
|
const TIMING_KEYS = ["first_action_s", "first_check_s", "detect_actions", "detect_s", "recovery_calls", "resume_actions"];
|
|
212
|
-
export const DERIVED_KEYS = ["signals", "analyzer_version", "verification", "unconfirmed", "delivery", "recovery", "delegation", "context", "routing", "steering", "tools", "complexity", "oversight", "planning", "changes", "autonomy", "authorship", "framing", "quality", "risk", "tool_mix", "reads", "context_files", "stage", "timing", "hygiene", "route", "adaptation", "reach", "interrupts", "steers", "utc_offset"];
|
|
229
|
+
export const DERIVED_KEYS = ["signals", "analyzer_version", "verification", "unconfirmed", "delivery", "recovery", "delegation", "context", "routing", "steering", "tools", "complexity", "oversight", "planning", "changes", "autonomy", "authorship", "framing", "quality", "risk", "tool_mix", "reads", "context_files", "stage", "timing", "hygiene", "route", "adaptation", "reach", "outcomes", "retrieval", "interrupts", "steers", "utc_offset"];
|
|
213
230
|
export function supplementFor(line, earlier) {
|
|
214
231
|
const missing = DERIVED_KEYS.filter((key) => line[key] !== undefined && !earlier.some((old) => old[key] !== undefined));
|
|
215
232
|
if (missing.length === 0) return null;
|
package/stretch-evidence.mjs
CHANGED
|
@@ -99,6 +99,34 @@ export const readKindOf = (block) => {
|
|
|
99
99
|
return READ_KINDS.find(([, pattern]) => pattern.test(path))?.[0] ?? "source";
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* A CHECK WHOSE OUTPUT GOES THROUGH A PIPE (0.9.8): `tsc | tail -3` exits with tail's code, so the exit code says nothing
|
|
104
|
+
* about the check (68% of the owner's checks were so). Outside quotes, the piece holding the check pipes into another
|
|
105
|
+
* command and no pipefail is set. Its outcome is then read from its output's words, here and never kept.
|
|
106
|
+
*/
|
|
107
|
+
/** A shell command that is a search (0.9.9): its first word, outside quotes; the pattern is never kept. */
|
|
108
|
+
export const shellSearch = (block) => {
|
|
109
|
+
if (!SHELL_TOOLS.has(String(block?.name ?? ""))) return false;
|
|
110
|
+
const raw = block.input?.command ?? block.input?.CommandLine ?? block.input?.cmd;
|
|
111
|
+
return typeof raw === "string" && raw.split(/;|&&|\n/).some((piece) => /^\s*(rg|grep|egrep|fgrep|ag|ack|fd|find|git\s+grep|locate)\b/.test(piece.split("|")[0]));
|
|
112
|
+
};
|
|
113
|
+
/** A shell command that reads a file (cat, sed -n, head, tail, less, bat): after a search, it found something (0.9.9). */
|
|
114
|
+
export const shellReads = (block) => {
|
|
115
|
+
if (!SHELL_TOOLS.has(String(block?.name ?? ""))) return false;
|
|
116
|
+
const raw = block.input?.command ?? block.input?.CommandLine ?? block.input?.cmd;
|
|
117
|
+
return typeof raw === "string" && /^\s*(cat|sed\s+-n|head|tail|less|bat|nl|awk)\b/.test(raw.split("|")[0]);
|
|
118
|
+
};
|
|
119
|
+
export const pipedCheck = (block) => {
|
|
120
|
+
if (!SHELL_TOOLS.has(String(block?.name ?? ""))) return false;
|
|
121
|
+
const raw = block.input?.command ?? block.input?.CommandLine ?? block.input?.cmd;
|
|
122
|
+
if (typeof raw !== "string" || /pipefail/.test(raw)) return false;
|
|
123
|
+
const bare = raw.replace(/'[^']*'/g, "''").replace(/"(?:[^"\\]|\\.)*"/g, '""');
|
|
124
|
+
return bare.split(/;|&&|\n/).some((piece) => /\|(?!\|)/.test(piece.replace(/\|\|/g, "")) && callKindsOf({ name: "Bash", input: { command: piece.split("|")[0] } }).some((kind) => CHECK_KINDS.includes(kind)));
|
|
125
|
+
};
|
|
126
|
+
const FAIL_WORDS = /\berror TS\d+|\b\d+ (errors?|failed|failing)\b(?<!\b0 errors?)(?<!\b0 failed)|\bFAIL(ED)?\b|✗|✖|\bAssertionError\b|\bELIFECYCLE\b|\bnpm ERR!|\bTraceback\b|\b(gate|verify|exit)=[1-9]/;
|
|
127
|
+
const PASS_WORDS = /\bPASS(ED)?\b|\bpassed\b|✓|✔|\b0 (errors?|failed|problems?)\b|\b(gate|verify|exit|tsc)=0\b|\ball tests passed\b|\bassertions hold\b/i;
|
|
128
|
+
export const outputVerdict = (text) => (FAIL_WORDS.test(text) ? "fail" : PASS_WORDS.test(text) ? "pass" : null);
|
|
129
|
+
|
|
102
130
|
/**
|
|
103
131
|
* A TOOL RESULT AS THE STRETCH READS IT (0.8.1): its call's id, whether it failed, and, read here and never kept, WHO
|
|
104
132
|
* refused it when it was refused: the PERSON (Claude Code's fixed sentence when they decline a tool use or a plan) or a
|
|
@@ -114,13 +142,14 @@ export const resultOf = (block) => {
|
|
|
114
142
|
if (!failed && block.outcome === "unknown") return { id: block.tool_use_id, failed: null, refusal: null };
|
|
115
143
|
// 0.9.3: an OUTAGE (the model or a tool overloaded, rate-limited, timed out, unreachable), read here and never kept.
|
|
116
144
|
const outage = failed && OUTAGE.test(text);
|
|
117
|
-
|
|
145
|
+
const words = outputVerdict(text), silent = /^\s*(\((Bash|Command|Tool) (completed|ran|returned) with no output\)|no output)?\s*$/i.test(text);
|
|
146
|
+
return { id: block.tool_use_id, failed, refusal: failed && PERSON_REFUSED.test(text) ? "person" : failed && GUARD_REFUSED.test(text) ? "guard" : null, ...(outage ? { outage: true } : {}), ...(words ? { words } : {}), ...(silent ? { silent: true } : {}) };
|
|
118
147
|
};
|
|
119
148
|
/** A to-do list the agent wrote (TodoWrite): how many items and how many done, never what they say. */
|
|
120
149
|
const planOf = (block) => (block.name === "TodoWrite" && Array.isArray(block.input?.todos) ? { items: block.input.todos.length, done: block.input.todos.filter((todo) => todo && todo.status === "completed").length } : null);
|
|
121
150
|
|
|
122
151
|
/** One tool call as the stretch reads it: its id, its kinds, its FAMILY (the tool and its check kinds) and a digest of its input (compared, never kept). */
|
|
123
|
-
export const callOf = (block) => { const kinds = callKindsOf(block), plan = planOf(block), marks = marksOf(block), read = readKindOf(block); return { id: block.id, kinds, ...(marks.length ? { marks } : {}), ...(read ? { read } : {}), family: `${String(block.name)}|${kinds.join("+")}`, digest: createHash("sha256").update(JSON.stringify(block.input ?? null)).digest("base64url").slice(0, 16), ...(plan ? { plan } : {}) }; };
|
|
152
|
+
export const callOf = (block) => { const kinds = callKindsOf(block), plan = planOf(block), marks = marksOf(block), read = readKindOf(block), piped = pipedCheck(block), search = shellSearch(block), shellRead = shellReads(block); return { id: block.id, kinds, ...(marks.length ? { marks } : {}), ...(read ? { read } : {}), ...(piped ? { piped } : {}), ...(search ? { search } : {}), ...(shellRead ? { shellRead } : {}), family: `${String(block.name)}|${kinds.join("+")}`, digest: createHash("sha256").update(JSON.stringify(block.input ?? null)).digest("base64url").slice(0, 16), ...(plan ? { plan } : {}) }; };
|
|
124
153
|
|
|
125
154
|
/**
|
|
126
155
|
* VERIFICATION PER STRETCH (0.7.1; framework L7, L12): per kind of check, how many ran and how many failed; per step of
|
|
@@ -554,9 +583,90 @@ function reachOf(messages) {
|
|
|
554
583
|
return Object.keys(kept).length > 0 ? kept : null;
|
|
555
584
|
}
|
|
556
585
|
|
|
586
|
+
/**
|
|
587
|
+
* OUTCOMES OF THE STEPS (0.9.8): per kind of check, whether its FIRST run passed (first-pass, the quality of the first
|
|
588
|
+
* try); delivery attempts that did not go through (a push or deploy refused or failed); and what HELPED: a model change,
|
|
589
|
+
* and a new plan, after a failure, each followed by a passing check before the next failure. Counts and flags.
|
|
590
|
+
*/
|
|
591
|
+
function outcomesOf(messages) {
|
|
592
|
+
const results = new Map();
|
|
593
|
+
for (const message of messages) for (const result of message.results ?? []) results.set(result.id, result);
|
|
594
|
+
const firstPass = {}, out = { deliveries_failed: 0, escalations_helped: 0, replans_helped: 0 };
|
|
595
|
+
let model = null, failedAt = null, index = 0, pending = null; // pending: what came after the last failure ("model" | "plan")
|
|
596
|
+
for (const message of messages) {
|
|
597
|
+
if (message.bridge) continue;
|
|
598
|
+
if (message.type === "assistant" && message.model) { if (model && message.model !== model && failedAt !== null && index - failedAt <= 3 && !pending) pending = "model"; model = message.model; }
|
|
599
|
+
for (const call of message.calls ?? []) {
|
|
600
|
+
index += 1;
|
|
601
|
+
const result = results.get(call.id);
|
|
602
|
+
if (call.plan && failedAt !== null && !pending) pending = "plan";
|
|
603
|
+
const checks = call.kinds.filter((kind) => CHECK_KINDS.includes(kind));
|
|
604
|
+
for (const kind of checks) if (firstPass[kind] === undefined && (result?.failed === true || result?.failed === false)) firstPass[kind] = result.failed === false;
|
|
605
|
+
if (call.kinds.some((kind) => DELIVERY_KINDS.includes(kind)) && result?.failed === true && !result.refusal) out.deliveries_failed += 1;
|
|
606
|
+
if (checks.length > 0 && result?.failed === false && pending) { if (pending === "model") out.escalations_helped += 1; else out.replans_helped += 1; pending = null; failedAt = null; }
|
|
607
|
+
if (result?.failed === true) { failedAt = index; pending = null; }
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const kept = Object.fromEntries(Object.entries(out).filter(([, value]) => value > 0));
|
|
611
|
+
if (Object.keys(firstPass).length > 0) kept.first_pass = firstPass;
|
|
612
|
+
return Object.keys(kept).length > 0 ? kept : null;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* RETRIEVAL AND GROUNDING (0.9.9): searches (grep, rg, find, git grep, Grep, Glob, a web search, an MCP search or
|
|
617
|
+
* retrieval tool), the same search again, a search followed by a read or a change (it found something) against another
|
|
618
|
+
* search straight after (again), searches before the first change and right after a failure, and deliveries
|
|
619
|
+
* with docs or tests read, or a search, since the last change (grounded). What was searched is never kept.
|
|
620
|
+
*/
|
|
621
|
+
const SEARCH_TOOLS = new Set(["Grep", "Glob", "WebSearch", "web_search", "grep_search", "file_search", "codebase_search", "search_files", "find_files"]);
|
|
622
|
+
const SEARCH_COMMAND = /^\s*(rg|grep|egrep|fgrep|ag|ack|fd|find|git\s+grep|locate)\b/;
|
|
623
|
+
const isSearch = (call) => {
|
|
624
|
+
const tool = call.family.split("|")[0];
|
|
625
|
+
return SEARCH_TOOLS.has(tool) || (/^mcp__/.test(tool) && /search|query|retriev|vector|semantic|lookup/i.test(tool)) || call.search === true;
|
|
626
|
+
};
|
|
627
|
+
function retrievalOf(messages) {
|
|
628
|
+
const results = new Map();
|
|
629
|
+
for (const message of messages) for (const result of message.results ?? []) results.set(result.id, result);
|
|
630
|
+
const out = { searches: 0, repeated: 0, found: 0, again: 0, before_change: 0, after_failure: 0, deliveries: 0, grounded: 0 };
|
|
631
|
+
const seen = new Set();
|
|
632
|
+
let previousSearch = false, changed = false, sinceFailure = null, groundedSinceChange = false;
|
|
633
|
+
for (const message of messages) {
|
|
634
|
+
if (message.bridge) continue;
|
|
635
|
+
for (const call of message.calls ?? []) {
|
|
636
|
+
const tool = call.family.split("|")[0], result = results.get(call.id), search = isSearch(call);
|
|
637
|
+
if (previousSearch) { if (search) out.again += 1; else if (tool === "Read" || EDIT_TOOLS.has(tool) || call.shellRead) out.found += 1; }
|
|
638
|
+
if (search) {
|
|
639
|
+
out.searches += 1;
|
|
640
|
+
if (seen.has(call.digest)) out.repeated += 1; else seen.add(call.digest);
|
|
641
|
+
if (!changed) out.before_change += 1;
|
|
642
|
+
if (sinceFailure !== null && sinceFailure <= 3) out.after_failure += 1;
|
|
643
|
+
groundedSinceChange = true;
|
|
644
|
+
}
|
|
645
|
+
if (call.read === "docs" || call.read === "tests") groundedSinceChange = true;
|
|
646
|
+
if (EDIT_TOOLS.has(tool)) { changed = true; groundedSinceChange = false; }
|
|
647
|
+
if (call.kinds.some((kind) => DELIVERY_KINDS.includes(kind)) && result?.failed === false) { out.deliveries += 1; if (groundedSinceChange) out.grounded += 1; }
|
|
648
|
+
previousSearch = search;
|
|
649
|
+
sinceFailure = result?.failed === true ? 0 : sinceFailure === null ? null : sinceFailure + 1;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return out.searches > 0 || out.deliveries > 0 ? Object.fromEntries(Object.entries(out).filter(([, value]) => value > 0)) : null;
|
|
653
|
+
}
|
|
654
|
+
|
|
557
655
|
/** The stretch's whole derived record; `helpers` are the hook's own readers of a human turn, so both read it the same way. */
|
|
558
|
-
|
|
656
|
+
/** A piped check's outcome from its output's words: failed or passed when they say so, unknown (null) when they do not. */
|
|
657
|
+
function settlePiped(messages) {
|
|
658
|
+
const piped = new Map();
|
|
659
|
+
for (const message of messages) for (const call of message.calls ?? []) if (call.piped) piped.set(call.id, call.kinds);
|
|
660
|
+
if (piped.size === 0) return messages;
|
|
661
|
+
// A type check and a linter print nothing when they pass: silence behind the pipe is their pass, no other check's.
|
|
662
|
+
const quietPass = (kinds) => kinds.some((kind) => kind === "typecheck" || kind === "lint") && !kinds.some((kind) => !["typecheck", "lint"].includes(kind) && CHECK_KINDS.includes(kind));
|
|
663
|
+
const settle = (result) => ({ ...result, failed: result.words === "fail" ? true : result.words === "pass" ? false : result.silent && quietPass(piped.get(result.id)) ? false : null });
|
|
664
|
+
return messages.map((message) => (message.results?.some((result) => piped.has(result.id)) ? { ...message, results: message.results.map((result) => (piped.has(result.id) && !result.refusal ? settle(result) : result)) } : message));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export function deriveStretch(raw, helpers) {
|
|
668
|
+
const messages = settlePiped(raw);
|
|
559
669
|
const context = contextOf(messages, helpers), routing = routingOf(messages), steering = steeringOf(messages, helpers);
|
|
560
|
-
const timing = timingOf(messages, helpers), hygiene = hygieneOf(messages), route = routeOf(messages), adaptation = adaptationOf(messages), reach = reachOf(messages), tools = toolsOf(messages), oversight = oversightOf(messages), planning = planningOf(messages), autonomy = autonomyOf(messages), practice = practiceOf(messages, helpers);
|
|
561
|
-
return { ...(practice ?? {}), ...(oversight ? { oversight } : {}), ...(planning ? { planning } : {}), ...(autonomy ? { autonomy } : {}), ...verificationOf(messages, helpers), ...(context ? { context } : {}), ...(routing ? { routing } : {}), ...(steering ? { steering } : {}), ...(tools ? { tools } : {}), ...(timing ? { timing } : {}), ...(hygiene ? { hygiene } : {}), ...(route ? { route } : {}), ...(adaptation ? { adaptation } : {}), ...(reach ? { reach } : {}) };
|
|
670
|
+
const timing = timingOf(messages, helpers), hygiene = hygieneOf(messages), route = routeOf(messages), adaptation = adaptationOf(messages), reach = reachOf(messages), outcomes = outcomesOf(messages), retrieval = retrievalOf(messages), tools = toolsOf(messages), oversight = oversightOf(messages), planning = planningOf(messages), autonomy = autonomyOf(messages), practice = practiceOf(messages, helpers);
|
|
671
|
+
return { ...(practice ?? {}), ...(oversight ? { oversight } : {}), ...(planning ? { planning } : {}), ...(autonomy ? { autonomy } : {}), ...verificationOf(messages, helpers), ...(context ? { context } : {}), ...(routing ? { routing } : {}), ...(steering ? { steering } : {}), ...(tools ? { tools } : {}), ...(timing ? { timing } : {}), ...(hygiene ? { hygiene } : {}), ...(route ? { route } : {}), ...(adaptation ? { adaptation } : {}), ...(reach ? { reach } : {}), ...(outcomes ? { outcomes } : {}), ...(retrieval ? { retrieval } : {}) };
|
|
562
672
|
}
|
package/worktrust.mjs
CHANGED
|
@@ -73,7 +73,7 @@ const command = args.find((arg, at) => !arg.startsWith("--") && !(at > 0 && VALU
|
|
|
73
73
|
const flag = (name) => { const at = args.indexOf(`--${name}`); return at >= 0 ? args[at + 1] : undefined; };
|
|
74
74
|
const has = (name) => args.includes(`--${name}`);
|
|
75
75
|
/** This CLI's version, said to the door so the app can tell which computer runs an old one (check-cli-package holds it equal to package.json). */
|
|
76
|
-
const CLI_VERSION = "0.9.
|
|
76
|
+
const CLI_VERSION = "0.9.9";
|
|
77
77
|
const ORIGIN = (flag("origin") ?? process.env.WORKTRUST_ORIGIN ?? "https://app.worktrust.io").replace(/\/$/, "");
|
|
78
78
|
const MCP = flag("url") ?? process.env.WORKTRUST_MCP_URL ?? `${ORIGIN}/api/mcp`;
|
|
79
79
|
const HOME_DIR = join(homedir(), ".worktrust");
|