codiedev 0.10.0-beta.15 → 0.10.0-beta.17

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/connect.js CHANGED
@@ -223,6 +223,9 @@ async function runConnect(args = []) {
223
223
  deviceName: saved?.deviceName || os.hostname(),
224
224
  captureIgnoredPaths: saved?.captureIgnoredPaths ?? [],
225
225
  ...(auth ? { auth } : {}),
226
+ features: {
227
+ projects: responseData.features?.projects === true,
228
+ },
226
229
  };
227
230
  try {
228
231
  (0, utils_1.writeConfig)(config);
package/dist/hook.js CHANGED
@@ -53,6 +53,7 @@ const launchContext_1 = require("./launchContext");
53
53
  const selfUpdate_1 = require("./selfUpdate");
54
54
  const version_1 = require("./version");
55
55
  const sync_1 = require("./packs/sync");
56
+ const unreadNotice_1 = require("./unreadNotice");
56
57
  const captureLog_1 = require("./captureLog");
57
58
  const subagents_1 = require("./subagents");
58
59
  const captureExclusions_1 = require("./captureExclusions");
@@ -61,6 +62,7 @@ const copilotHooks_1 = require("./copilotHooks");
61
62
  const piHooks_1 = require("./piHooks");
62
63
  const cliSession_1 = require("./cliSession");
63
64
  const utils_2 = require("./utils");
65
+ const promptGuard_1 = require("./promptGuard");
64
66
  function buildDiscoveryContext(skills) {
65
67
  if (skills.length === 0)
66
68
  return null;
@@ -222,19 +224,20 @@ async function postSessionSkip(config, sessionId, reason) {
222
224
  async function runSessionStart(hookInput, source = "claude-code") {
223
225
  const config = (0, utils_1.readConfig)();
224
226
  if (!config)
225
- return;
227
+ return undefined;
226
228
  if (hookInput.session_id) {
227
229
  try {
228
- await postJson(`${config.backendUrl}/api/cli/sessionStart`, {
230
+ const res = await postJsonForResponse(`${config.backendUrl}/api/cli/sessionStart`, {
229
231
  sessionId: hookInput.session_id,
230
232
  startedAt: Date.now(),
231
233
  source,
232
234
  }, config.token, PING_TIMEOUT_MS);
235
+ return res?.unreadPings;
233
236
  }
234
237
  catch {
235
238
  }
236
239
  }
237
- return;
240
+ return undefined;
238
241
  }
239
242
  function postJson(url, body, bearerToken, timeoutMs) {
240
243
  return new Promise((resolve, reject) => {
@@ -478,6 +481,40 @@ async function main() {
478
481
  if (process.argv[2]?.startsWith("security-"))
479
482
  process.exit(0);
480
483
  const captureIgnored = (0, captureExclusions_1.shouldIgnoreCaptureInput)((0, utils_1.readConfig)(), hookInput);
484
+ if (process.argv[2] === "prompt-guard-refresh") {
485
+ await (0, cliSession_1.ensureFreshToken)({ configPath: utils_2.CONFIG_PATH }).catch(() => undefined);
486
+ await (0, promptGuard_1.refreshPromptGuardPolicy)((0, utils_1.readConfig)());
487
+ process.exit(0);
488
+ }
489
+ const PROMPT_HOOKS = {
490
+ discover: "claude-code",
491
+ "discover-codex": "codex",
492
+ "discover-codie": "codie-harness",
493
+ "discover-copilot": "copilot",
494
+ "discover-pi": "pi",
495
+ "prompt-guard-cursor": "cursor",
496
+ };
497
+ const promptHarness = PROMPT_HOOKS[process.argv[2] ?? ""];
498
+ if (promptHarness) {
499
+ const guard = (0, promptGuard_1.checkPrompt)(hookInput.prompt, promptHarness, (0, promptGuard_1.readPromptGuardPolicy)());
500
+ (0, promptGuard_1.maybeSpawnPromptGuardRefresh)({ connected: Boolean((0, utils_1.readConfig)()) });
501
+ if (guard.blocked) {
502
+ (0, captureLog_1.appendCaptureLog)({
503
+ stage: "prompt-guard",
504
+ sessionId: hookInput.session_id ?? "unknown",
505
+ outcome: "ok",
506
+ detail: `blocked harness=${promptHarness} rule=${guard.key.rule}`,
507
+ });
508
+ process.stdout.write(JSON.stringify(guard.response));
509
+ if (!captureIgnored)
510
+ await (0, promptGuard_1.reportPromptBlocked)((0, utils_1.readConfig)(), guard.key.rule, promptHarness);
511
+ process.exit(0);
512
+ }
513
+ if (promptHarness === "cursor") {
514
+ process.stdout.write(JSON.stringify({ continue: true }));
515
+ process.exit(0);
516
+ }
517
+ }
481
518
  const promptCommand = process.argv[2] ?? "";
482
519
  if (process.argv[2] === "discover" ||
483
520
  process.argv[2] === "discover-codex" ||
@@ -501,6 +538,8 @@ async function main() {
501
538
  (0, sync_1.maybeSpawnPackSync)({ connected: Boolean((0, utils_1.readConfig)()), cli: path.join(__dirname, "cli.js") });
502
539
  if (source === "codex")
503
540
  (0, localScrub_1.maybeSpawnLocalScrub)({ connected: Boolean((0, utils_1.readConfig)()), cwd: hookInput.cwd });
541
+ if ((0, launchContext_1.isScriptedPrompt)(source, hookInput))
542
+ return;
504
543
  if (source !== "copilot")
505
544
  await runDiscover(hookInput.prompt);
506
545
  return;
@@ -543,10 +582,11 @@ async function main() {
543
582
  process.argv[2] === "session-start-pi") {
544
583
  (0, sync_1.maybeSpawnPackSync)({ connected: Boolean((0, utils_1.readConfig)()), cli: path.join(__dirname, "cli.js") });
545
584
  (0, localScrub_1.maybeSpawnLocalScrub)({ connected: Boolean((0, utils_1.readConfig)()), cwd: hookInput.cwd });
585
+ (0, promptGuard_1.maybeSpawnPromptGuardRefresh)({ connected: Boolean((0, utils_1.readConfig)()) });
546
586
  const packNag = process.argv[2] === "session-start-opencode" || process.argv[2] === "session-start-pi" ? null : (0, sync_1.packNagLine)();
547
587
  if (packNag && process.argv[2] === "session-start")
548
588
  process.stdout.write(`${packNag}\n`);
549
- await runSessionStart(hookInput, process.argv[2] === "session-start-opencode"
589
+ const unreadPings = await runSessionStart(hookInput, process.argv[2] === "session-start-opencode"
550
590
  ? "opencode"
551
591
  : process.argv[2] === "session-start-cursor"
552
592
  ? "cursor"
@@ -555,13 +595,18 @@ async function main() {
555
595
  : process.argv[2] === "session-start-pi"
556
596
  ? "pi"
557
597
  : "claude-code");
598
+ const unreadNotice = process.argv[2] === "session-start-opencode" || process.argv[2] === "session-start-pi"
599
+ ? null
600
+ : (0, unreadNotice_1.takeUnreadNotice)(unreadPings);
601
+ if (unreadNotice && process.argv[2] === "session-start")
602
+ process.stdout.write(`${unreadNotice}\n`);
558
603
  if ((0, utils_1.readConfig)() && (process.argv[2] === "session-start-cursor" || process.argv[2] === "session-start-copilot")) {
559
604
  const source = process.argv[2] === "session-start-cursor" ? "cursor" : "copilot";
560
605
  const { TEAM_SKILL_CONTEXT, teamSkillSessionOutput } = await Promise.resolve().then(() => __importStar(require("./teamSkillContext")));
561
606
  const prompt = hookInput.initialPrompt
562
607
  ?? hookInput.initial_prompt;
563
608
  const discovered = typeof prompt === "string" ? await discoverContext(prompt) : null;
564
- process.stdout.write(JSON.stringify(teamSkillSessionOutput(source, [TEAM_SKILL_CONTEXT, discovered, packNag].filter(Boolean).join("\n\n"))));
609
+ process.stdout.write(JSON.stringify(teamSkillSessionOutput(source, [TEAM_SKILL_CONTEXT, discovered, packNag, unreadNotice].filter(Boolean).join("\n\n"))));
565
610
  }
566
611
  return;
567
612
  }
@@ -1,7 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fileList = fileList;
3
4
  exports.formatInbox = formatInbox;
4
5
  const shared_1 = require("./commands/shared");
6
+ function fileSize(bytes) {
7
+ if (bytes < 1024)
8
+ return `${bytes} B`;
9
+ if (bytes < 1024 * 1024)
10
+ return `${Math.round(bytes / 1024)} KB`;
11
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
12
+ }
13
+ function fileList(files) {
14
+ return files.map((f) => `${f.name} (${fileSize(f.size)})`).join(", ");
15
+ }
5
16
  function formatInbox(opts) {
6
17
  const mentions = opts.mentions ?? [];
7
18
  const DEFAULT_LIMIT = 50;
@@ -25,6 +36,9 @@ function formatInbox(opts) {
25
36
  const where = p.subjectKey ? ` · on ${p.subjectKey}` : "";
26
37
  lines.push(`${dot} ${from}${where} · ${(0, shared_1.timeAgo)(p.createdAt)}`);
27
38
  lines.push(` ${p.preview || "(no body)"}`);
39
+ if (p.attachments?.length) {
40
+ lines.push(` ${p.attachments.length === 1 ? "file" : "files"}: ${fileList(p.attachments)}`);
41
+ }
28
42
  lines.push(` id: ${p.pingId}`);
29
43
  lines.push("");
30
44
  }
@@ -39,7 +53,9 @@ function formatInbox(opts) {
39
53
  lines.push(` ${m.url}`);
40
54
  lines.push("");
41
55
  }
42
- if (keptPings.length > 0)
56
+ if (keptPings.length > 0) {
43
57
  lines.push("Mark read: codiedev read <ping-id>");
58
+ lines.push('Reply: codiedev reply <ping-id> "<message>"');
59
+ }
44
60
  return lines;
45
61
  }
@@ -7,7 +7,10 @@ exports.windowsAncestors = windowsAncestors;
7
7
  exports.detectCursorLaunch = detectCursorLaunch;
8
8
  exports.detectCopilotLaunch = detectCopilotLaunch;
9
9
  exports.hookLaunch = hookLaunch;
10
+ exports.codexLaunchFromRolloutHead = codexLaunchFromRolloutHead;
11
+ exports.isScriptedPrompt = isScriptedPrompt;
10
12
  const node_child_process_1 = require("node:child_process");
13
+ const node_fs_1 = require("node:fs");
11
14
  const VALUE = /^[a-z0-9][a-z0-9_.:-]{0,39}$/;
12
15
  const CODIE_SUBCOMMANDS = ["run", "serve", "acp", "web", "attach"];
13
16
  function codieLaunchFromArgv(argv) {
@@ -148,3 +151,56 @@ function hookLaunch(hookCommand, input, deps = {}) {
148
151
  }
149
152
  return undefined;
150
153
  }
154
+ const SCRIPTED_LAUNCHES = new Set([
155
+ "codex:exec", "codex:mcp",
156
+ "codie:run", "codie:serve", "codie:acp",
157
+ "cursor:print", "copilot:print",
158
+ "pi:print", "pi:json", "pi:rpc",
159
+ ]);
160
+ const isScriptedLaunch = (launch) => launch.startsWith("sdk") || SCRIPTED_LAUNCHES.has(launch);
161
+ function codexLaunchFromRolloutHead(head) {
162
+ const end = head.indexOf("\n");
163
+ if (end < 0)
164
+ return undefined;
165
+ try {
166
+ const first = JSON.parse(head.slice(0, end));
167
+ if (first?.type !== "session_meta")
168
+ return undefined;
169
+ const src = first.payload?.source;
170
+ if (typeof src === "string")
171
+ return VALUE.test(`codex:${src}`) ? `codex:${src}` : undefined;
172
+ if (src && typeof src === "object")
173
+ return `codex:${Object.keys(src)[0] ?? "other"}`;
174
+ }
175
+ catch {
176
+ }
177
+ return undefined;
178
+ }
179
+ function readHead(file, bytes = 256 * 1024) {
180
+ let fd;
181
+ try {
182
+ fd = (0, node_fs_1.openSync)(file, "r");
183
+ const buf = Buffer.alloc(bytes);
184
+ return buf.toString("utf8", 0, (0, node_fs_1.readSync)(fd, buf, 0, bytes, 0));
185
+ }
186
+ catch {
187
+ return "";
188
+ }
189
+ finally {
190
+ if (fd !== undefined)
191
+ (0, node_fs_1.closeSync)(fd);
192
+ }
193
+ }
194
+ function isScriptedPrompt(harness, input, deps = {}) {
195
+ if (typeof input.launch === "string" && VALUE.test(input.launch))
196
+ return isScriptedLaunch(input.launch);
197
+ if (harness === "claude-code") {
198
+ const entrypoint = (deps.env ?? process.env).CLAUDE_CODE_ENTRYPOINT;
199
+ return typeof entrypoint === "string" && entrypoint.startsWith("sdk");
200
+ }
201
+ if (harness === "codex" && typeof input.transcript_path === "string" && input.transcript_path) {
202
+ const launch = codexLaunchFromRolloutHead((deps.readHead ?? readHead)(input.transcript_path));
203
+ return launch !== undefined && isScriptedLaunch(launch);
204
+ }
205
+ return false;
206
+ }
package/dist/launcher.js CHANGED
@@ -37,15 +37,17 @@ exports.LAUNCHER_FILE = void 0;
37
37
  exports.launcherPath = launcherPath;
38
38
  exports.usesLauncher = usesLauncher;
39
39
  exports.readInstallRecord = readInstallRecord;
40
+ exports.isInTempDir = isInTempDir;
40
41
  exports.stableHookScript = stableHookScript;
41
42
  exports.recordCurrentInstall = recordCurrentInstall;
42
43
  exports.ensureLauncher = ensureLauncher;
43
44
  exports.resetLauncherCacheForTests = resetLauncherCacheForTests;
44
45
  const fs = __importStar(require("fs"));
46
+ const os = __importStar(require("os"));
45
47
  const path = __importStar(require("path"));
46
48
  const configDir_1 = require("./configDir");
47
49
  exports.LAUNCHER_FILE = "codiedev-hook-launcher.cjs";
48
- const LAUNCHER_REVISION = 1;
50
+ const LAUNCHER_REVISION = 2;
49
51
  function launcherPath() {
50
52
  return path.join((0, configDir_1.codiedevStateDir)(), "bin", exports.LAUNCHER_FILE);
51
53
  }
@@ -67,6 +69,12 @@ try {
67
69
  if (rec && typeof rec.hookScript === "string" && fs.existsSync(rec.hookScript)) target = rec.hookScript;
68
70
  } catch {}
69
71
  if (!target && fs.existsSync(FALLBACK)) target = FALLBACK;
72
+ if (!target) {
73
+ // Both stranded (e.g. a deleted temp build): use a global install next to this node.
74
+ const prefix = path.dirname(path.dirname(process.execPath));
75
+ target = [path.join(prefix, "lib", "node_modules", "codiedev", "dist", "hook.js"),
76
+ path.join(prefix, "node_modules", "codiedev", "dist", "hook.js")].find((p) => fs.existsSync(p)) || null;
77
+ }
70
78
  if (!target) process.exit(0);
71
79
  process.env.CODIEDEV_VIA_LAUNCHER = "1";
72
80
  process.argv[1] = target;
@@ -97,11 +105,26 @@ function packageVersionFor(hookScript) {
97
105
  return undefined;
98
106
  }
99
107
  }
108
+ function isInTempDir(p, tmp = os.tmpdir()) {
109
+ const real = (x) => {
110
+ try {
111
+ return fs.realpathSync(x);
112
+ }
113
+ catch {
114
+ return path.resolve(x);
115
+ }
116
+ };
117
+ const rel = path.relative(real(tmp), real(p));
118
+ return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
119
+ }
100
120
  function stableHookScript(hookScript, globalCandidates = globalHookScripts()) {
101
121
  if (!hookScript || !path.isAbsolute(hookScript) || hookScript.includes(`${path.sep}~${path.sep}`))
102
122
  return null;
103
123
  if (!fs.existsSync(hookScript))
104
124
  return null;
125
+ if (process.env.CODIEDEV_ALLOW_TEMP_HOOK !== "1" && isInTempDir(hookScript)) {
126
+ return globalCandidates.find((p) => path.isAbsolute(p) && fs.existsSync(p) && !isInTempDir(p)) ?? null;
127
+ }
105
128
  if (hookScript.includes(`${path.sep}_npx${path.sep}`)) {
106
129
  const global = globalCandidates.find((p) => path.isAbsolute(p) && fs.existsSync(p));
107
130
  if (global)
@@ -97,6 +97,12 @@ function sessionStores(env = process.env, home = os.homedir(), stateDir = (0, co
97
97
  root: path.join((0, copilotHooks_1.copilotHome)(env, home), "session-state"),
98
98
  match: (f) => path.basename(f) === "workspace.yaml",
99
99
  },
100
+ {
101
+ harness: "copilot",
102
+ kind: "json",
103
+ root: path.join((0, copilotHooks_1.copilotHome)(env, home), "session-state"),
104
+ match: (f) => path.basename(f) === "index.json" && path.basename(path.dirname(f)) === "rewind-file-snapshots",
105
+ },
100
106
  { harness: "pi", kind: "jsonl", root: path.join((0, harnessDirs_1.piAgentDir)(env, home), "sessions"), match: isJsonl },
101
107
  { harness: "pi", kind: "jsonl", root: path.join(stateDir, "managed", "pi", "agent", "sessions"), match: isJsonl },
102
108
  { harness: "opencode", kind: "json", root: path.join(stateDir, "opencode-captures"), match: (f) => f.endsWith(".json") },
@@ -182,8 +182,10 @@ function sessionKeyOf(harness, file) {
182
182
  const base = path.basename(file);
183
183
  if (base === "history.jsonl")
184
184
  return "history";
185
- if (harness === "copilot")
186
- return path.basename(path.dirname(file));
185
+ if (harness === "copilot") {
186
+ const dir = path.dirname(file);
187
+ return path.basename(path.basename(dir) === "rewind-file-snapshots" ? path.dirname(dir) : dir);
188
+ }
187
189
  const uuid = base.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
188
190
  if (uuid)
189
191
  return uuid[1];
package/dist/managedPi.js CHANGED
@@ -190,6 +190,7 @@ function prepareManagedPiDir(opts) {
190
190
  });
191
191
  fs.rmSync(path.join(agentDir, "auth.json"), { force: true });
192
192
  fs.rmSync(path.join(agentDir, "extensions"), { recursive: true, force: true });
193
+ fs.writeFileSync(path.join(agentDir, "AGENTS.md"), (0, utils_1.codiedevInstructionsText)(), { mode: 0o600 });
193
194
  const guard = path.join(agentDir, exports.GUARD_FILE);
194
195
  fs.writeFileSync(guard, exports.GUARD_SOURCE, { mode: 0o600 });
195
196
  const capture = path.join(agentDir, exports.CAPTURE_FILE);
package/dist/mcp.js CHANGED
@@ -158,10 +158,10 @@ const TOOLS = [
158
158
  },
159
159
  {
160
160
  name: "codiedev_ping",
161
- description: "Send a message to a teammate on Codiedev, optionally attached to a " +
162
- "specific artifact. Use when the user asks to ping, ask, share with, " +
163
- "or get feedback from a teammate by name (e.g. 'ping a teammate about " +
164
- "this spec'). Recipients are resolved by first name, full name, or email " +
161
+ description: "Send a message to a teammate on Codiedev, optionally with an " +
162
+ "artifact (they also get read access to it). Use when the user asks to " +
163
+ "ping, ask, send something to, or get feedback from a teammate by name " +
164
+ "(e.g. 'send this spec to a teammate'). Recipients are resolved by first name, full name, or email " +
165
165
  "within the current org. They're notified by email and can reply " +
166
166
  "from their own agent.",
167
167
  inputSchema: {
@@ -178,8 +178,13 @@ const TOOLS = [
178
178
  },
179
179
  subjectKey: {
180
180
  type: "string",
181
- description: "Optional filename key of an artifact the message is about " +
182
- "(e.g. 'spec-214.md'). Leaving blank sends a standalone ping.",
181
+ description: "Optional filename key of an artifact to send (e.g. " +
182
+ "'spec-214.md'). The teammate also gets read access to it. " +
183
+ "Leaving blank sends a standalone ping.",
184
+ },
185
+ edit: {
186
+ type: "boolean",
187
+ description: "With subjectKey: give edit access instead of read (owner only).",
183
188
  },
184
189
  },
185
190
  required: ["to", "body"],
@@ -229,7 +234,7 @@ const TOOLS = [
229
234
  "without sending a notification. Use when the user says 'share this " +
230
235
  "with a teammate' or 'give someone access', and doesn't want to surface " +
231
236
  "it as a message. If they want the teammate actively notified, use " +
232
- "codiedev_send_to instead.",
237
+ "codiedev_ping with subjectKey instead.",
233
238
  inputSchema: {
234
239
  type: "object",
235
240
  properties: {
@@ -248,36 +253,13 @@ const TOOLS = [
248
253
  required: ["filename", "to"],
249
254
  },
250
255
  },
251
- {
252
- name: "codiedev_send_to",
253
- description: "Grant access to an artifact AND send a short message to a " +
254
- "teammate. Use when the user asks to 'send this to a teammate', 'ask " +
255
- "someone to look at this', or 'share this with a teammate and tell them X'. " +
256
- "This is the active handoff version of codiedev_share_with — the " +
257
- "recipient gets a ping in their inbox.",
258
- inputSchema: {
259
- type: "object",
260
- properties: {
261
- filename: { type: "string", description: "Artifact filename." },
262
- to: { type: "string", description: "Teammate first name, full name, or email." },
263
- message: {
264
- type: "string",
265
- description: "Optional short note to send with the share. If omitted, only " +
266
- "the grant is created (no ping).",
267
- },
268
- },
269
- required: ["filename", "to"],
270
- },
271
- },
272
256
  {
273
257
  name: "codiedev_search",
274
258
  description: "Search the team's artifact library by natural-language query. Use " +
275
259
  "when the user asks 'has anyone solved X?', 'find me the spec on Y', " +
276
260
  "or 'look for prior work on Z'. Always search before pushing a new " +
277
261
  "artifact so the user doesn't duplicate work. Returns ranked hits " +
278
- "with title, type, filename key, and a snippet. For open-ended " +
279
- "questions where the user wants a synthesized answer (not just a list " +
280
- "of hits), prefer codiedev_ask instead.",
262
+ "with title, type, filename key, and a snippet.",
281
263
  inputSchema: {
282
264
  type: "object",
283
265
  properties: {
@@ -287,32 +269,6 @@ const TOOLS = [
287
269
  required: ["query"],
288
270
  },
289
271
  },
290
- {
291
- name: "codiedev_ask",
292
- description: "Ask a natural-language question of the team's collective memory and " +
293
- "get a synthesized answer with citations. Searches across both " +
294
- "auto-extracted artifacts (specs / decisions / bugfixes) AND raw " +
295
- "session transcripts, then has an LLM compose an answer that cites " +
296
- "the actual sources. Use when the user asks 'why did we…?', 'what " +
297
- "does the team know about X?', 'has anyone done Y here before?', " +
298
- "'who decided Z and why?', or any open-ended question about prior " +
299
- "work where they want context, not just a hit list. For exact-match " +
300
- "lookups (find me spec-x), prefer codiedev_pull. For browsing or " +
301
- "filtering by kind/tag, prefer codiedev_get_library. For surfacing " +
302
- "ranked hits without LLM synthesis, prefer codiedev_search.",
303
- inputSchema: {
304
- type: "object",
305
- properties: {
306
- question: {
307
- type: "string",
308
- description: "Natural-language question. Open-ended is fine — e.g. 'why " +
309
- "did we drop pg-bouncer?' or 'how does the team think about " +
310
- "feature flags?'.",
311
- },
312
- },
313
- required: ["question"],
314
- },
315
- },
316
272
  {
317
273
  name: "codiedev_get_library",
318
274
  description: "List artifacts in the team library — both docs (spec / decision / " +
@@ -442,8 +398,6 @@ async function dispatchTool(name, args, config) {
442
398
  return await handleShareWith(args, config);
443
399
  case "codiedev_send_to":
444
400
  return await handleSendTo(args, config);
445
- case "codiedev_ask":
446
- return await handleAsk(args, config);
447
401
  case "codiedev_search":
448
402
  return await handleSearch(args, config);
449
403
  case "codiedev_get_library":
@@ -533,12 +487,13 @@ async function handlePing(args, config) {
533
487
  const to = asString(args.to);
534
488
  const body = asString(args.body);
535
489
  const subjectKey = asStringOrUndefined(args.subjectKey);
490
+ const grant = subjectKey ? (args.edit === true ? "edit" : "read") : undefined;
536
491
  if (!to)
537
492
  throw new Error("to required");
538
493
  if (!body)
539
494
  throw new Error("body required");
540
495
  try {
541
- const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ping", { config, body: { to, body, subjectKey } });
496
+ const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ping", { config, body: { to, body, subjectKey, ...(grant ? { grant } : {}) } });
542
497
  return {
543
498
  content: [
544
499
  {
@@ -704,23 +659,6 @@ async function handleSearch(args, config) {
704
659
  }
705
660
  return { content: [{ type: "text", text: lines.join("\n") }] };
706
661
  }
707
- async function handleAsk(args, config) {
708
- const question = asString(args.question);
709
- if (!question)
710
- throw new Error("question required");
711
- const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ask", { config, body: { question } });
712
- const lines = [res.answer];
713
- if (res.citations && res.citations.length > 0) {
714
- lines.push("", "Sources:");
715
- for (const c of res.citations) {
716
- const label = c.kind === "artifact"
717
- ? `[${c.type}] ${c.title}`
718
- : `[session] ${c.title}`;
719
- lines.push(` • ${label}`);
720
- }
721
- }
722
- return { content: [{ type: "text", text: lines.join("\n") }] };
723
- }
724
662
  async function handleGetLibrary(args, config) {
725
663
  const scope = asStringOrUndefined(args.scope);
726
664
  const type = asStringOrUndefined(args.type);