@creator-notes/cnotes 0.24.3 → 0.25.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/dist/commands/auth.d.ts +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +72 -8
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/canvas.d.ts.map +1 -1
- package/dist/commands/canvas.js +122 -20
- package/dist/commands/canvas.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +10 -3
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/operations.d.ts.map +1 -1
- package/dist/commands/operations.js +21 -19
- package/dist/commands/operations.js.map +1 -1
- package/dist/commands/workspace.d.ts.map +1 -1
- package/dist/commands/workspace.js +171 -1
- package/dist/commands/workspace.js.map +1 -1
- package/dist/lib/api-client.d.ts +35 -9
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +60 -24
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/auth-store.d.ts +50 -11
- package/dist/lib/auth-store.d.ts.map +1 -1
- package/dist/lib/auth-store.js +47 -12
- package/dist/lib/auth-store.js.map +1 -1
- package/dist/lib/install-mcp.d.ts.map +1 -1
- package/dist/lib/install-mcp.js +19 -1
- package/dist/lib/install-mcp.js.map +1 -1
- package/dist/lib/output.d.ts +7 -0
- package/dist/lib/output.d.ts.map +1 -1
- package/dist/lib/output.js +16 -0
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/server-url.d.ts +33 -0
- package/dist/lib/server-url.d.ts.map +1 -0
- package/dist/lib/server-url.js +106 -0
- package/dist/lib/server-url.js.map +1 -0
- package/package.json +3 -3
- package/skills/cnotes/SKILL.md +59 -4
package/dist/commands/auth.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface LoginResult {
|
|
|
4
4
|
email: string;
|
|
5
5
|
serverUrl: string;
|
|
6
6
|
}
|
|
7
|
-
export declare function browserLogin(
|
|
7
|
+
export declare function browserLogin(rawServerUrl: string, json: boolean, options?: {
|
|
8
8
|
quiet?: boolean;
|
|
9
9
|
}): Promise<LoginResult>;
|
|
10
10
|
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyI3D;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5B,OAAO,CAAC,WAAW,CAAC,CA6ItB"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
2
3
|
import { loadConfig, saveConfig, setServerConfig, removeServerConfig, getServerConfig, } from "../lib/auth-store.js";
|
|
4
|
+
import { validateServerUrl, unofficialServerWarning, allowedCallbackOrigins, } from "../lib/server-url.js";
|
|
5
|
+
import { readStdin } from "../lib/stdin.js";
|
|
3
6
|
import { handleError } from "../lib/errors.js";
|
|
4
7
|
import { outputJson } from "../lib/output.js";
|
|
5
8
|
export function registerAuthCommands(program) {
|
|
@@ -10,15 +13,42 @@ export function registerAuthCommands(program) {
|
|
|
10
13
|
.command("login")
|
|
11
14
|
.description("Authenticate with CreatorNotes via browser OAuth")
|
|
12
15
|
.option("--token <token>", "Paste an existing API key instead of browser flow")
|
|
16
|
+
.option("--token-stdin", "Read the API key from stdin (safer than --token, which is visible in `ps` and shell history)")
|
|
13
17
|
.option("--server <url>", "Server URL to authenticate with")
|
|
14
18
|
.action(async (opts) => {
|
|
15
19
|
const json = program.opts().json ?? false;
|
|
16
20
|
try {
|
|
17
21
|
const config = loadConfig();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
// Reject malformed/insecure server URLs before we ever send a key there.
|
|
23
|
+
const serverUrl = validateServerUrl(opts.server || process.env.CNOTES_SERVER || config.currentServer);
|
|
24
|
+
// Resolve a directly-supplied token from the safe source first.
|
|
25
|
+
let token;
|
|
26
|
+
if (opts.tokenStdin) {
|
|
27
|
+
// On a TTY there is nothing piped in, so readStdin() blocks on EOF with
|
|
28
|
+
// no output — indistinguishable from a hang. Say what we're waiting for.
|
|
29
|
+
if (process.stdin.isTTY && !json) {
|
|
30
|
+
console.error("Reading the API key from stdin — paste it and press Ctrl-D, " +
|
|
31
|
+
"or pipe it in (e.g. `pbpaste | cnotes auth login --token-stdin`).");
|
|
32
|
+
}
|
|
33
|
+
token = (await readStdin()).trim();
|
|
34
|
+
if (!token)
|
|
35
|
+
throw new Error("No token received on stdin.");
|
|
36
|
+
}
|
|
37
|
+
else if (opts.token) {
|
|
38
|
+
token = opts.token;
|
|
39
|
+
// argv is world-visible via `ps` and lands in shell history — the key
|
|
40
|
+
// is a bearer credential, so nudge toward the stdin path (HIGH-2).
|
|
41
|
+
if (!json) {
|
|
42
|
+
console.error("Warning: --token is visible in `ps` output and your shell history. " +
|
|
43
|
+
"Prefer `--token-stdin` (pipe the key in) for a credential.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (token) {
|
|
47
|
+
// The key is about to be sent to serverUrl to validate — warn if that
|
|
48
|
+
// host isn't official/localhost, the phishing moment (MEDIUM-2).
|
|
49
|
+
const warning = unofficialServerWarning(serverUrl);
|
|
50
|
+
if (warning && !json)
|
|
51
|
+
console.error(warning);
|
|
22
52
|
// Verify the token works by calling a simple endpoint
|
|
23
53
|
const res = await fetch(new URL("/api/workspaces", serverUrl).toString(), {
|
|
24
54
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -104,13 +134,38 @@ export function registerAuthCommands(program) {
|
|
|
104
134
|
}
|
|
105
135
|
});
|
|
106
136
|
}
|
|
107
|
-
export async function browserLogin(
|
|
137
|
+
export async function browserLogin(rawServerUrl, json, options) {
|
|
108
138
|
const quiet = options?.quiet ?? false;
|
|
139
|
+
// Validate HERE, not only at the call sites: `cnotes init` (and any future
|
|
140
|
+
// caller) feeds `config.currentServer` straight in, so a URL stored before
|
|
141
|
+
// set-server validated anything — a trailing slash, or a cleartext http remote
|
|
142
|
+
// — would otherwise skip every check and, if malformed, die on `new URL(...)`
|
|
143
|
+
// below with a far less legible error. Validating at the entry point makes
|
|
144
|
+
// every caller inherit it, and normalizes what we then persist.
|
|
145
|
+
const serverUrl = validateServerUrl(rawServerUrl);
|
|
146
|
+
// Single-use nonce tying the callback to THIS login attempt. Without it, any
|
|
147
|
+
// web page open in the browser during the ~2-minute login window could scan
|
|
148
|
+
// localhost ports and POST its own token to our /callback — a login-CSRF that
|
|
149
|
+
// would silently authenticate the CLI as the ATTACKER's account (so the
|
|
150
|
+
// user's next `cnotes notes create` writes into the attacker's workspace).
|
|
151
|
+
// The auth page echoes this value back; a mismatch is rejected (HIGH-1).
|
|
152
|
+
const expectedState = randomBytes(16).toString("hex");
|
|
153
|
+
const allowedOrigins = allowedCallbackOrigins(serverUrl);
|
|
109
154
|
return new Promise((resolve, reject) => {
|
|
110
155
|
const server = createServer((req, res) => {
|
|
111
156
|
const url = new URL(req.url || "/", `http://localhost`);
|
|
112
|
-
// CORS
|
|
113
|
-
|
|
157
|
+
// Scope CORS to the server origin (not "*") so no other site can read this
|
|
158
|
+
// callback's response or drive it cross-origin (HIGH-1). Echo back the
|
|
159
|
+
// request's own Origin when it's one we allow — for a local dev server the
|
|
160
|
+
// page may arrive from the loopback SIBLING (the /auth/cli page rewrites
|
|
161
|
+
// 127.0.0.1 -> localhost so OAuth cookies resolve), and a single hard-coded
|
|
162
|
+
// origin would mismatch it. Login would still complete (CORS gates reading
|
|
163
|
+
// the response, not sending the GET), but the page would show the manual
|
|
164
|
+
// screen for no reason.
|
|
165
|
+
const reqOrigin = req.headers.origin;
|
|
166
|
+
const originToAllow = reqOrigin && allowedOrigins.has(reqOrigin) ? reqOrigin : allowedOrigins.values().next().value;
|
|
167
|
+
res.setHeader("Access-Control-Allow-Origin", originToAllow);
|
|
168
|
+
res.setHeader("Vary", "Origin");
|
|
114
169
|
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
115
170
|
if (req.method === "OPTIONS") {
|
|
116
171
|
res.writeHead(204);
|
|
@@ -124,6 +179,15 @@ export async function browserLogin(serverUrl, json, options) {
|
|
|
124
179
|
}
|
|
125
180
|
const token = url.searchParams.get("token");
|
|
126
181
|
const email = url.searchParams.get("email");
|
|
182
|
+
const state = url.searchParams.get("state");
|
|
183
|
+
// Reject a callback that doesn't carry our nonce. An older auth page that
|
|
184
|
+
// doesn't echo `state` fails here too, but the page's own fallback then
|
|
185
|
+
// shows the manual `--token` command, so the user is never hard-blocked.
|
|
186
|
+
if (!state || state !== expectedState) {
|
|
187
|
+
res.writeHead(400);
|
|
188
|
+
res.end("Invalid or missing state");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
127
191
|
if (!token) {
|
|
128
192
|
res.writeHead(400);
|
|
129
193
|
res.end("Missing token");
|
|
@@ -161,7 +225,7 @@ export async function browserLogin(serverUrl, json, options) {
|
|
|
161
225
|
return;
|
|
162
226
|
}
|
|
163
227
|
const port = addr.port;
|
|
164
|
-
const authUrl = `${serverUrl}/auth/cli?callbackPort=${port}`;
|
|
228
|
+
const authUrl = `${serverUrl}/auth/cli?callbackPort=${port}&state=${expectedState}`;
|
|
165
229
|
if (!quiet && !json) {
|
|
166
230
|
console.log("Opening browser for authentication...");
|
|
167
231
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EACL,UAAU,EACV,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,MAAM,IAAI,GAAG,OAAO;SACjB,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,uBAAuB,CAAC,CAAC;IAExC,IAAI;SACD,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,kDAAkD,CAAC;SAC/D,MAAM,CAAC,iBAAiB,EAAE,mDAAmD,CAAC;SAC9E,MAAM,CAAC,gBAAgB,EAAE,iCAAiC,CAAC;SAC3D,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,KAAK,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,MAAM,CAAC,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/commands/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/commands/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAwF5C,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA44D7D"}
|
package/dist/commands/canvas.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Option } from "commander";
|
|
2
|
-
import { ApiClient } from "../lib/api-client.js";
|
|
2
|
+
import { ApiClient, resolveActiveAgentRun } from "../lib/api-client.js";
|
|
3
3
|
import { resolveConfig } from "../lib/config.js";
|
|
4
|
-
import { EXIT_CODES, handleError, ValidationError } from "../lib/errors.js";
|
|
4
|
+
import { CliError, EXIT_CODES, handleError, ValidationError } from "../lib/errors.js";
|
|
5
5
|
import { parseJsonOption, readFileSafe } from "../lib/fs-utils.js";
|
|
6
|
-
import {
|
|
6
|
+
import { loadConfig, setActiveAgentRunId, clearActiveAgentRunId, getActiveAgentRunId, } from "../lib/auth-store.js";
|
|
7
|
+
import { outputJson, outputTable, outputQuiet, truncate, timeAgo, formatAgo } from "../lib/output.js";
|
|
7
8
|
import { readCanvasMarkdown, renderCanvasMarkdown } from "../lib/canvas-read.js";
|
|
8
9
|
import { resolveAgentSession } from "../lib/claude-session.js";
|
|
9
10
|
import { WARN, dim } from "../lib/style.js";
|
|
@@ -1520,33 +1521,88 @@ Examples:
|
|
|
1520
1521
|
// Bracket a sequence of canvas commands with auto-checkpoints so the
|
|
1521
1522
|
// server treats them as a single agent operation. The pattern:
|
|
1522
1523
|
//
|
|
1523
|
-
//
|
|
1524
|
+
// npx cnotes canvas agent-run begin --canvas <id> --prompt "Reorganize"
|
|
1524
1525
|
// npx cnotes canvas add-node ...
|
|
1525
1526
|
// npx cnotes canvas add-edge ...
|
|
1526
1527
|
// npx cnotes canvas agent-run end --canvas <id>
|
|
1527
1528
|
//
|
|
1528
|
-
// `begin`
|
|
1529
|
-
//
|
|
1530
|
-
//
|
|
1531
|
-
//
|
|
1529
|
+
// `begin` PERSISTS the run as the active run for this server (same store
|
|
1530
|
+
// `cnotes operations begin` uses), so every subsequent cnotes write attaches
|
|
1531
|
+
// automatically — across separate shells and stateless tool calls, with no
|
|
1532
|
+
// env var to thread through. It also still prints `export CNOTES_AGENT_RUN_ID=...`
|
|
1533
|
+
// so the older `eval "$(...)"` form keeps working; the env var wins over the
|
|
1534
|
+
// persisted pointer in ApiClient's precedence order.
|
|
1535
|
+
//
|
|
1536
|
+
// Persisting is load-bearing, not a convenience. Before it, an agent that ran
|
|
1537
|
+
// begin and then issued each write as its own process (the normal shape of
|
|
1538
|
+
// agent work) got NO run attached: the writes silently landed as
|
|
1539
|
+
// `manual.edit` / actorSource "human", and `end` failed with "No agent run ID
|
|
1540
|
+
// found". The bracket looked fine and recorded a lie.
|
|
1541
|
+
//
|
|
1542
|
+
// Two things persist, and BOTH are required for "Revert this run" to work:
|
|
1543
|
+
// the runId (which clusters the writes into one operation) and the
|
|
1544
|
+
// before-snapshot id (which the run's first mutation back-links as
|
|
1545
|
+
// operation.checkpointBeforeId — the state revert rewinds to). Persist only
|
|
1546
|
+
// the first and you get a run that looks healthy in the timeline and throws
|
|
1547
|
+
// "Operation cannot be reverted" when the user tries to undo it.
|
|
1532
1548
|
const agentRun = canvas
|
|
1533
1549
|
.command("agent-run")
|
|
1534
1550
|
.description("Bracket canvas commands with before/after auto-checkpoints");
|
|
1535
1551
|
agentRun
|
|
1536
1552
|
.command("begin")
|
|
1537
|
-
.description("Open an agent-run session — captures a before-snapshot and
|
|
1553
|
+
.description("Open an agent-run session — captures a before-snapshot and makes the run active for this server")
|
|
1538
1554
|
.requiredOption("--canvas <id>", "Canvas ID to bracket")
|
|
1539
1555
|
.requiredOption("--prompt <text>", "User prompt or summary describing the run")
|
|
1540
1556
|
.option("--rationale <text>", "Optional rationale (e.g. agent's plan)")
|
|
1557
|
+
.option("--force", "If an active run already exists on this server, end it server-side and start a fresh one")
|
|
1558
|
+
.addHelpText("after", `
|
|
1559
|
+
Examples:
|
|
1560
|
+
$ cnotes canvas agent-run begin --canvas <id> --prompt "Reorganize into sections"
|
|
1561
|
+
$ cnotes canvas place <id> --spec ./layout.json # attaches automatically
|
|
1562
|
+
$ cnotes canvas agent-run end --canvas <id>
|
|
1563
|
+
|
|
1564
|
+
After 'begin', every subsequent cnotes write on this server attaches to the
|
|
1565
|
+
run automatically — no env var to export, and it survives across separate
|
|
1566
|
+
shells. End with 'cnotes canvas agent-run end --canvas <id>'.
|
|
1567
|
+
|
|
1568
|
+
If a run is already open (from 'agent-run begin' or 'operations begin'),
|
|
1569
|
+
begin refuses rather than silently clobbering it. Pass --force to end the
|
|
1570
|
+
prior run server-side and start fresh.
|
|
1571
|
+
`)
|
|
1541
1572
|
.action(async (opts) => {
|
|
1542
1573
|
const cfg = resolveConfig(program.opts());
|
|
1543
1574
|
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
1544
|
-
// This command is designed to be
|
|
1575
|
+
// This command is designed to be usable via `eval "$(...)"`, which makes
|
|
1545
1576
|
// stdout a non-TTY. resolveConfig's default would then flip cfg.json on
|
|
1546
1577
|
// and we'd emit JSON instead of shell exports — breaking the eval form.
|
|
1547
1578
|
// Honour JSON only when the user asked for it explicitly.
|
|
1548
1579
|
const wantsJson = program.opts().json === true || process.env.CNOTES_JSON === "1";
|
|
1549
1580
|
try {
|
|
1581
|
+
// Refuse to open a second run while one is still active on this server.
|
|
1582
|
+
// Both `operations begin` and `canvas agent-run begin` write the same
|
|
1583
|
+
// pointer, and a request carries exactly one X-Agent-Run-Id — so a
|
|
1584
|
+
// second begin would either clobber the first or silently attach writes
|
|
1585
|
+
// to the wrong run. Mirrors `cnotes operations begin`.
|
|
1586
|
+
const existing = getActiveAgentRunId(loadConfig(), cfg.serverUrl);
|
|
1587
|
+
if (existing) {
|
|
1588
|
+
if (opts.force) {
|
|
1589
|
+
try {
|
|
1590
|
+
await api.post("/api/operations", {
|
|
1591
|
+
action: "end",
|
|
1592
|
+
agentRunId: existing.agentRunId,
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
catch {
|
|
1596
|
+
// Best-effort: ending an already-completed/abandoned run is
|
|
1597
|
+
// idempotent server-side. Either way clear the local pointer so
|
|
1598
|
+
// the caller isn't wedged.
|
|
1599
|
+
}
|
|
1600
|
+
clearActiveAgentRunId(loadConfig(), cfg.serverUrl);
|
|
1601
|
+
}
|
|
1602
|
+
else {
|
|
1603
|
+
throw new CliError(`An active agent-run already exists on this server (agentRunId ${existing.agentRunId}, started ${formatAgo(existing.startedAt)}).`, EXIT_CODES.CONFLICT, "End it first (`cnotes canvas agent-run end --canvas <id>` or `cnotes operations end`), or pass --force to end it server-side and start fresh.");
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1550
1606
|
const body = {
|
|
1551
1607
|
action: "agentRunBegin",
|
|
1552
1608
|
prompt: opts.prompt,
|
|
@@ -1557,6 +1613,29 @@ Examples:
|
|
|
1557
1613
|
if (agentSession)
|
|
1558
1614
|
body.agentSession = agentSession;
|
|
1559
1615
|
const data = await api.post(`/api/canvas/${encodeURIComponent(opts.canvas)}`, body);
|
|
1616
|
+
// Persist the active run so subsequent cnotes writes attach to it even
|
|
1617
|
+
// when each one is a separate process. The server response carries no
|
|
1618
|
+
// startedAt for canvas runs, so stamp it locally — it only feeds the
|
|
1619
|
+
// "started Xs ago" text on a conflicting begin.
|
|
1620
|
+
//
|
|
1621
|
+
// beforeCheckpointId rides along on every write as
|
|
1622
|
+
// X-Agent-Before-Snapshot-Id: the server can only back-link
|
|
1623
|
+
// operation.checkpointBeforeId at the run's FIRST mutation (at begin
|
|
1624
|
+
// time the operation row doesn't exist yet), and without that link
|
|
1625
|
+
// getRevertContext bails — "Revert this run" is dead for the whole
|
|
1626
|
+
// batch. Persisting the run id without it produces a run that looks
|
|
1627
|
+
// perfectly healthy and cannot be undone.
|
|
1628
|
+
const persisted = setActiveAgentRunId(loadConfig(), cfg.serverUrl, data.agentRunId, Date.now(), data.beforeCheckpointId);
|
|
1629
|
+
// No stored config entry for this server (token came from --token /
|
|
1630
|
+
// CNOTES_TOKEN against a server never logged into), so there is nowhere
|
|
1631
|
+
// to record the run. Say so rather than printing "every write now
|
|
1632
|
+
// attaches" — silently attaching nothing is the bug this flow exists to
|
|
1633
|
+
// kill. stderr, so it can't corrupt --json or the eval form.
|
|
1634
|
+
if (!persisted) {
|
|
1635
|
+
console.error(`# WARNING: could not persist the active run for ${cfg.serverUrl} — no stored login for that server.`);
|
|
1636
|
+
console.error(`# Subsequent cnotes writes will NOT attach to it. Either run \`cnotes auth login\`,`);
|
|
1637
|
+
console.error(`# or use the env form: eval "$(cnotes canvas agent-run begin --canvas ${opts.canvas} --prompt "...")"`);
|
|
1638
|
+
}
|
|
1560
1639
|
if (cfg.quiet) {
|
|
1561
1640
|
outputQuiet(data.agentRunId);
|
|
1562
1641
|
return;
|
|
@@ -1565,14 +1644,16 @@ Examples:
|
|
|
1565
1644
|
outputJson(data);
|
|
1566
1645
|
return;
|
|
1567
1646
|
}
|
|
1568
|
-
//
|
|
1647
|
+
// Still print the eval-friendly exports so the older
|
|
1569
1648
|
// eval "$(npx cnotes canvas agent-run begin ...)"
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
1572
|
-
// the operation row at agentRunBegin time.
|
|
1649
|
+
// form keeps working. The env var takes precedence over the persisted
|
|
1650
|
+
// pointer, so both paths agree on the same run.
|
|
1573
1651
|
console.log(`export CNOTES_AGENT_RUN_ID=${data.agentRunId}`);
|
|
1574
1652
|
console.log(`export CNOTES_AGENT_BEFORE_SNAPSHOT_ID=${data.beforeCheckpointId}`);
|
|
1575
1653
|
console.error(`# agent run started: v${data.beforeSnapshotNumber} (${data.agentRunId})`);
|
|
1654
|
+
if (persisted) {
|
|
1655
|
+
console.error(`# every cnotes write on this server now attaches to this run`);
|
|
1656
|
+
}
|
|
1576
1657
|
console.error(`# end with: npx cnotes canvas agent-run end --canvas ${opts.canvas}`);
|
|
1577
1658
|
}
|
|
1578
1659
|
catch (error) {
|
|
@@ -1612,6 +1693,14 @@ Examples:
|
|
|
1612
1693
|
// need to propagate — prompt + rationale are persisted on the
|
|
1613
1694
|
// operation row at agentRunBegin time, so resending them per-request
|
|
1614
1695
|
// would be redundant (and would add a spoofing surface).
|
|
1696
|
+
//
|
|
1697
|
+
// Unlike `begin`, wrap deliberately does NOT persist the run to the
|
|
1698
|
+
// server config: the env var scopes it to this subprocess tree, and wrap
|
|
1699
|
+
// always closes the run in its own finally-path. Persisting would risk
|
|
1700
|
+
// leaving a stale pointer if wrap were killed, and would make wrap
|
|
1701
|
+
// unusable inside an open `operations` run (a common nesting: open an
|
|
1702
|
+
// operation for `notes create`, then wrap a canvas layout inside it).
|
|
1703
|
+
// Env var beats the persisted pointer in ApiClient, so the nesting works.
|
|
1615
1704
|
const { spawn } = await import("node:child_process");
|
|
1616
1705
|
const child = spawn(command[0], command.slice(1), {
|
|
1617
1706
|
stdio: "inherit",
|
|
@@ -1663,24 +1752,37 @@ Examples:
|
|
|
1663
1752
|
});
|
|
1664
1753
|
agentRun
|
|
1665
1754
|
.command("end")
|
|
1666
|
-
.description("Close an agent-run session — captures an after-snapshot and
|
|
1755
|
+
.description("Close an agent-run session — captures an after-snapshot and clears the active run")
|
|
1667
1756
|
.requiredOption("--canvas <id>", "Canvas ID to close")
|
|
1668
|
-
.option("--run-id <id>", "Agent run ID (defaults to
|
|
1757
|
+
.option("--run-id <id>", "Agent run ID (defaults to the active run on this server, then CNOTES_AGENT_RUN_ID)")
|
|
1669
1758
|
.action(async (opts) => {
|
|
1670
1759
|
const cfg = resolveConfig(program.opts());
|
|
1671
1760
|
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
1672
|
-
const runId = opts.runId ?? process.env.CNOTES_AGENT_RUN_ID;
|
|
1673
|
-
if (!runId) {
|
|
1674
|
-
throw new ValidationError("No agent run ID found. Pass --run-id or set CNOTES_AGENT_RUN_ID.");
|
|
1675
|
-
}
|
|
1676
1761
|
// Same reasoning as `begin`: eval form makes stdout non-TTY, so don't
|
|
1677
1762
|
// let the auto-detect flip us into JSON mode.
|
|
1678
1763
|
const wantsJson = program.opts().json === true || process.env.CNOTES_JSON === "1";
|
|
1679
1764
|
try {
|
|
1765
|
+
// Resolve the run to close through the SAME resolver ApiClient uses to
|
|
1766
|
+
// attach it (explicit flag first, then env, then the persisted run).
|
|
1767
|
+
// Sharing the function, rather than re-deriving the precedence here, is
|
|
1768
|
+
// the point: `end` closing a run the client would not have attached to
|
|
1769
|
+
// is exactly the desync that let agent edits record as human ones.
|
|
1770
|
+
const runId = opts.runId ?? resolveActiveAgentRun(cfg.serverUrl)?.agentRunId;
|
|
1771
|
+
if (!runId) {
|
|
1772
|
+
throw new ValidationError("No active run on this server, and no --run-id passed", "Open one with `cnotes canvas agent-run begin --canvas <id> --prompt \"...\"`, or pass --run-id <id> to close a specific run.");
|
|
1773
|
+
}
|
|
1680
1774
|
const data = await api.post(`/api/canvas/${encodeURIComponent(opts.canvas)}`, {
|
|
1681
1775
|
action: "agentRunEnd",
|
|
1682
1776
|
agentRunId: runId,
|
|
1683
1777
|
});
|
|
1778
|
+
// Clear the local pointer so the next write detaches. Only clear when
|
|
1779
|
+
// it matches the run we just closed, so a concurrent begin from another
|
|
1780
|
+
// shell isn't stomped. Done after a successful response so a transient
|
|
1781
|
+
// server error can't desync local state from the server.
|
|
1782
|
+
const active = getActiveAgentRunId(loadConfig(), cfg.serverUrl);
|
|
1783
|
+
if (active?.agentRunId === data.agentRunId) {
|
|
1784
|
+
clearActiveAgentRunId(loadConfig(), cfg.serverUrl);
|
|
1785
|
+
}
|
|
1684
1786
|
if (cfg.quiet) {
|
|
1685
1787
|
outputQuiet(String(data.afterSnapshotNumber));
|
|
1686
1788
|
return;
|