codiedev 0.9.88 → 0.9.90

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.
@@ -2,7 +2,7 @@ import * as fs from "fs";
2
2
  export declare const CAPTURE_LOG_MAX_BYTES: number;
3
3
  export declare const CAPTURE_LOG_PATH: string;
4
4
  export interface CaptureLogEntry {
5
- stage: "capture-finish" | "upload" | "upload-deferred" | "stderr" | "limit" | "self-update";
5
+ stage: "capture-finish" | "upload" | "upload-deferred" | "stderr" | "limit" | "self-update" | "prompt-guard";
6
6
  sessionId: string | undefined;
7
7
  outcome: string;
8
8
  detail?: string;
package/dist/cli.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * codiedev connect — link agent CLIs to your org
7
7
  * codiedev push <file> — author/update a spec/review/decision/etc.
8
8
  * codiedev pull <key> — fetch an artifact by filename key
9
- * codiedev ping <user> "<msg>" — send a message (optionally --with <key>)
9
+ * codiedev ping <user> ["<msg>"] — message and/or share (--with <key>)
10
10
  * codiedev inbox — list pings (use --unread to filter)
11
11
  * codiedev read <ping-id> — mark read + show full message
12
12
  * codiedev reply <ping-id> "<msg>" — answer a ping in its thread
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * codiedev connect — link agent CLIs to your org
8
8
  * codiedev push <file> — author/update a spec/review/decision/etc.
9
9
  * codiedev pull <key> — fetch an artifact by filename key
10
- * codiedev ping <user> "<msg>" — send a message (optionally --with <key>)
10
+ * codiedev ping <user> ["<msg>"] — message and/or share (--with <key>)
11
11
  * codiedev inbox — list pings (use --unread to filter)
12
12
  * codiedev read <ping-id> — mark read + show full message
13
13
  * codiedev reply <ping-id> "<msg>" — answer a ping in its thread
@@ -103,11 +103,10 @@ Artifacts:
103
103
  Asks for confirmation; -y to skip.
104
104
 
105
105
  Messaging:
106
- codiedev ping <user> "<msg>" [--with <key>] [--file <path>]
107
- Send a message to a teammate
108
- codiedev send <key> <user> ["<msg>"] Share an artifact + ping in one step
109
- codiedev share <key> <user> [--role read|edit]
110
- Silently grant access (no notification)
106
+ codiedev ping <user> ["<msg>"] [--with <key>] [--edit] [--file <path>]
107
+ Message a teammate (emails them). --with
108
+ gives them access to an artifact; with
109
+ no message it's a silent share
111
110
  codiedev inbox [--unread] [--limit N] Show messages
112
111
  codiedev read <ping-id> Mark read + show full message
113
112
  codiedev reply <ping-id> "<msg>" [--file <path>]
@@ -280,10 +279,14 @@ codiedev pull — fetch an artifact
280
279
  codiedev pull spec-cart-clear.md | less
281
280
  `.trim(),
282
281
  ping: `
283
- codiedev ping — send a teammate a message, optionally attached to an artifact
282
+ codiedev ping — send a teammate a message, an artifact, or both
284
283
 
285
284
  Usage:
286
- codiedev ping <user> "<message>" [--with <artifact-key>] [--file <path>]
285
+ codiedev ping <user> ["<message>"] [--with <artifact-key>] [--edit] [--file <path>]
286
+
287
+ --with gives them read access to the artifact (--edit: edit access; only
288
+ the owner can give edit access or share a private skill). With no message,
289
+ it's a silent share: access only, no inbox entry, no email.
287
290
 
288
291
  <user> resolves to a teammate in your org by first name, full name,
289
292
  or email. Ambiguous matches return a list — retry with the full email.
package/dist/codie.js CHANGED
@@ -176,6 +176,57 @@ function installBrandWordmarkUnsafe(runtime) {
176
176
  (0, child_process_1.spawnSync)("codesign", ["--force", "--sign", "-", runtime], { stdio: "ignore" });
177
177
  }
178
178
  }
179
+ async function runNonInteractive(runtime, runtimeArgs, env) {
180
+ const detached = process.platform !== "win32";
181
+ const child = (0, child_process_1.spawn)(runtime, runtimeArgs, {
182
+ stdio: ["pipe", "pipe", "pipe"],
183
+ env,
184
+ detached,
185
+ });
186
+ if (child.stdin)
187
+ process.stdin.pipe(child.stdin);
188
+ const signalChild = (signal) => {
189
+ if (detached && child.pid) {
190
+ try {
191
+ process.kill(-child.pid, signal);
192
+ return;
193
+ }
194
+ catch {
195
+ // Fall back to the direct child if its process group already exited.
196
+ }
197
+ }
198
+ child.kill(signal);
199
+ };
200
+ const forwardInterrupt = () => signalChild("SIGINT");
201
+ const forwardTermination = () => signalChild("SIGTERM");
202
+ process.once("SIGINT", forwardInterrupt);
203
+ process.once("SIGTERM", forwardTermination);
204
+ return await new Promise((resolve, reject) => {
205
+ child.stdout?.on("data", (chunk) => process.stdout.write(chunk));
206
+ child.stderr?.on("data", (chunk) => {
207
+ // The Codiedev plugin stopped a prompt with a key in it: print its
208
+ // reason plainly instead of the private marker line.
209
+ const text = chunk.toString("utf8");
210
+ if (!text.includes(codieLauncher_1.PROMPT_BLOCK_MARKER))
211
+ return process.stderr.write(chunk);
212
+ process.stderr.write(text.split(/\r?\n/).map((line) => (0, codieLauncher_1.promptBlockLine)(line) ?? line).join("\n"));
213
+ });
214
+ child.once("error", (error) => {
215
+ if (child.stdin)
216
+ process.stdin.unpipe(child.stdin);
217
+ process.removeListener("SIGINT", forwardInterrupt);
218
+ process.removeListener("SIGTERM", forwardTermination);
219
+ reject(error);
220
+ });
221
+ child.once("close", (status) => {
222
+ if (child.stdin)
223
+ process.stdin.unpipe(child.stdin);
224
+ process.removeListener("SIGINT", forwardInterrupt);
225
+ process.removeListener("SIGTERM", forwardTermination);
226
+ resolve(status);
227
+ });
228
+ });
229
+ }
179
230
  async function main() {
180
231
  (0, codieRuntime_1.assertCodieNodeVersion)(process.versions.node);
181
232
  if (!process.env.CODIEDEV_GATEWAY_KEY && !process.env.CODIEDEV_TOKEN) {
@@ -303,6 +354,10 @@ async function main() {
303
354
  };
304
355
  const runtimeArgs = (0, codieLauncher_1.withCodieWorkingDirectory)(parsed.args, process.cwd());
305
356
  const nonInteractiveRun = runtimeArgs[0] === "run";
357
+ // A run has no TUI to show a stopped prompt; the plugin hands the reason
358
+ // back on stderr instead (see runNonInteractive).
359
+ if (nonInteractiveRun)
360
+ env.CODIEDEV_PROMPT_BLOCK_OUTPUT = "marker";
306
361
  if (nonInteractiveRun) {
307
362
  // The TUI paints its own live status line from the same endpoint. A
308
363
  // non-interactive run has no TUI, so the budget arrives as two printed
@@ -315,10 +370,7 @@ async function main() {
315
370
  if (line)
316
371
  console.error(line);
317
372
  }
318
- const result = (0, child_process_1.spawnSync)(runtime, runtimeArgs, { stdio: "inherit", env });
319
- if (result.error)
320
- throw result.error;
321
- process.exitCode = result.status === null ? 1 : result.status;
373
+ process.exitCode = (await runNonInteractive(runtime, runtimeArgs, env)) ?? 1;
322
374
  if (reportCost) {
323
375
  // The gateway books a request's cost after it has answered, so a poll
324
376
  // fired the instant the run closes can miss the last one.
@@ -89,3 +89,6 @@ export declare function parseCodieArgs(argv: string[]): {
89
89
  overrides: Record<string, string>;
90
90
  };
91
91
  export declare function validateCodieArgs(args: string[], catalog: CatalogModel[]): void;
92
+ export declare const PROMPT_BLOCK_MARKER = "__CODIEDEV_PROMPT_BLOCKED__";
93
+ /** The reason on a marker line, or null for any other line. */
94
+ export declare function promptBlockLine(line: string): string | null;
@@ -42,7 +42,7 @@ var __importStar = (this && this.__importStar) || (function () {
42
42
  };
43
43
  })();
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.HIDDEN_WORDMARK = exports.CODIEDEV_WORDMARK = exports.LEGACY_CODIEDEV_WORDMARK = exports.STOCK_WORDMARK = exports.BRAND_PLUGIN_SPEC = exports.USAGE_MODULE_PLACEHOLDER = void 0;
45
+ exports.PROMPT_BLOCK_MARKER = exports.HIDDEN_WORDMARK = exports.CODIEDEV_WORDMARK = exports.LEGACY_CODIEDEV_WORDMARK = exports.STOCK_WORDMARK = exports.BRAND_PLUGIN_SPEC = exports.USAGE_MODULE_PLACEHOLDER = void 0;
46
46
  exports.usableModelCost = usableModelCost;
47
47
  exports.usableModelLimit = usableModelLimit;
48
48
  exports.withCodieWorkingDirectory = withCodieWorkingDirectory;
@@ -56,6 +56,7 @@ exports.resolveCapturePlugin = resolveCapturePlugin;
56
56
  exports.fetchApprovedCatalog = fetchApprovedCatalog;
57
57
  exports.parseCodieArgs = parseCodieArgs;
58
58
  exports.validateCodieArgs = validateCodieArgs;
59
+ exports.promptBlockLine = promptBlockLine;
59
60
  const path = __importStar(require("path"));
60
61
  const url_1 = require("url");
61
62
  const harnessDirs_1 = require("./harnessDirs");
@@ -461,3 +462,17 @@ function validateCodieArgs(args, catalog) {
461
462
  }
462
463
  }
463
464
  }
465
+ exports.PROMPT_BLOCK_MARKER = "__CODIEDEV_PROMPT_BLOCKED__";
466
+ /** The reason on a marker line, or null for any other line. */
467
+ function promptBlockLine(line) {
468
+ const at = line.indexOf(exports.PROMPT_BLOCK_MARKER);
469
+ if (at < 0)
470
+ return null;
471
+ try {
472
+ const reason = JSON.parse(line.slice(at + exports.PROMPT_BLOCK_MARKER.length)).reason;
473
+ return typeof reason === "string" && reason.trim() ? `\n${reason.trim()}` : null;
474
+ }
475
+ catch {
476
+ return null;
477
+ }
478
+ }
@@ -1 +1,10 @@
1
- export declare function runPing(args: string[]): Promise<void>;
1
+ import type { CodiedevConfig } from "../utils";
2
+ export declare const PING_USAGE = "Usage: codiedev ping <user> [\"<message>\"] [--with <artifact-key>] [--edit] [--file <path>]";
3
+ export declare function parsePingArgs(allArgs: string[]): {
4
+ to: string;
5
+ body: string;
6
+ subjectKey?: string;
7
+ edit: boolean;
8
+ files: string[];
9
+ };
10
+ export declare function runPing(args: string[], configOverride?: CodiedevConfig): Promise<void>;
@@ -1,16 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PING_USAGE = void 0;
4
+ exports.parsePingArgs = parsePingArgs;
3
5
  exports.runPing = runPing;
6
+ // `codiedev ping <user> ["<message>"] [--with <artifact>] [--edit] [--file <path>]`
7
+ // The one way to send a teammate something:
8
+ // message only → inbox + email
9
+ // message + --with → inbox + email, and they get access to the artifact
10
+ // --with, no message → access only, no notification (what `share` did)
11
+ // `send` and `share` are kept as aliases that route here.
4
12
  const shared_1 = require("./shared");
5
13
  const pingFiles_1 = require("./pingFiles");
6
- function parseArgs(allArgs) {
14
+ const share_1 = require("./share");
15
+ exports.PING_USAGE = 'Usage: codiedev ping <user> ["<message>"] [--with <artifact-key>] [--edit] [--file <path>]';
16
+ function parsePingArgs(allArgs) {
7
17
  const { files, rest: args } = (0, pingFiles_1.takeFileArgs)(allArgs);
8
18
  let to;
9
19
  const positional = [];
10
20
  let subjectKey;
21
+ let edit = false;
11
22
  for (let i = 0; i < args.length; i++) {
12
23
  const a = args[i];
13
- if ((a === "--with" || a === "-w") && i + 1 < args.length) {
24
+ if (a === "--edit") {
25
+ edit = true;
26
+ }
27
+ else if ((a === "--with" || a === "-w") && i + 1 < args.length) {
14
28
  subjectKey = args[++i];
15
29
  }
16
30
  else if (a.startsWith("--with=")) {
@@ -20,26 +34,41 @@ function parseArgs(allArgs) {
20
34
  positional.push(a);
21
35
  }
22
36
  }
23
- // A file can stand in for the message.
24
- if (positional.length < 2 && !(positional.length === 1 && files.length > 0)) {
25
- console.error("Usage: codiedev ping <user> \"<message>\" [--with <artifact-key>] [--file <path>]");
37
+ // A file can stand in for the message; --with alone shares without one.
38
+ if (positional.length < 2 && !(positional.length === 1 && (files.length > 0 || subjectKey))) {
39
+ console.error(exports.PING_USAGE);
40
+ process.exit(1);
41
+ }
42
+ if (edit && !subjectKey) {
43
+ console.error("--edit needs an artifact: --with <artifact-key>");
26
44
  process.exit(1);
27
45
  }
28
46
  to = positional[0];
29
47
  // Join remaining positionals in case the shell split the message on spaces
30
48
  // despite quoting (happens with some escape sequences).
31
49
  const body = (0, shared_1.stripQuotes)(positional.slice(1).join(" "));
32
- return { to, body: body || (files.length ? "Sent a file." : ""), subjectKey, files };
50
+ return { to, body: body || (files.length ? "Sent a file." : ""), subjectKey, edit, files };
33
51
  }
34
- async function runPing(args) {
35
- const { to, body, subjectKey, files } = parseArgs(args);
36
- const config = (0, shared_1.requireConfig)();
52
+ async function runPing(args, configOverride) {
53
+ const { to, body, subjectKey, edit, files } = parsePingArgs(args);
54
+ // Nothing to say, just access: a silent share.
55
+ if (!body && subjectKey) {
56
+ await (0, share_1.runShare)([subjectKey, to, "--role", edit ? "edit" : "read"], configOverride);
57
+ return;
58
+ }
59
+ const config = configOverride ?? (0, shared_1.requireConfig)();
37
60
  const start = Date.now();
38
61
  try {
39
62
  const attachmentIds = await (0, pingFiles_1.uploadOrExit)(config, files);
40
63
  const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ping", {
41
64
  config,
42
- body: { to, body, subjectKey, ...(attachmentIds.length ? { attachmentIds } : {}) },
65
+ body: {
66
+ to,
67
+ body,
68
+ subjectKey,
69
+ ...(subjectKey ? { grant: edit ? "edit" : "read" } : {}),
70
+ ...(attachmentIds.length ? { attachmentIds } : {}),
71
+ },
43
72
  });
44
73
  (0, shared_1.recordAgentEvent)(config, {
45
74
  tool: "codiedev_ping",
@@ -1,2 +1,3 @@
1
1
  import type { CodiedevConfig } from "../utils";
2
+ export declare function sendToPingArgs(args: string[]): string[] | null;
2
3
  export declare function runSend(args: string[], configOverride?: CodiedevConfig): Promise<void>;
@@ -1,67 +1,27 @@
1
1
  "use strict";
2
- // `codiedev send <filename> <recipient> [message]` — share an artifact AND
3
- // send a short message that lands in the teammate's inbox. The active-
4
- // handoff version of `codiedev share`. Use this when the user is actively
5
- // looping someone in.
6
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendToPingArgs = sendToPingArgs;
7
4
  exports.runSend = runSend;
5
+ // `codiedev send <artifact> <recipient> ["<message>"]` — kept as an alias for
6
+ // `codiedev ping <recipient> ["<message>"] --with <artifact>`. With a message
7
+ // the teammate gets it in their inbox and by email plus read access; without
8
+ // one it's a silent share.
9
+ const ping_1 = require("./ping");
8
10
  const shared_1 = require("./shared");
9
- function parseArgs(args) {
11
+ function sendToPingArgs(args) {
10
12
  const positionals = args.filter((a) => !a.startsWith("--"));
11
13
  const [filename, to, ...rest] = positionals;
12
- if (!filename || !to) {
13
- console.error('Usage: codiedev send <filename> <recipient> ["<message>"]');
14
- process.exit(1);
15
- }
14
+ if (!filename || !to)
15
+ return null;
16
16
  // Recombine remaining tokens — supports both quoted and unquoted messages.
17
- const message = rest.length > 0 ? (0, shared_1.stripQuotes)(rest.join(" ")) : undefined;
18
- return { filename, to, message };
17
+ const message = rest.length > 0 ? (0, shared_1.stripQuotes)(rest.join(" ")) : "";
18
+ return [to, ...(message ? [message] : []), "--with", filename];
19
19
  }
20
20
  async function runSend(args, configOverride) {
21
- const parsed = parseArgs(args);
22
- const config = configOverride ?? (0, shared_1.requireConfig)();
23
- const start = Date.now();
24
- try {
25
- const body = {
26
- filename: parsed.filename,
27
- to: parsed.to,
28
- };
29
- if (parsed.message)
30
- body.message = parsed.message;
31
- const res = await (0, shared_1.apiRequest)("POST", "/api/cli/send-to", {
32
- config,
33
- body,
34
- });
35
- (0, shared_1.recordAgentEvent)(config, {
36
- tool: "codiedev_send_to",
37
- ok: true,
38
- latencyMs: Date.now() - start,
39
- });
40
- const who = res.recipient ? `${res.recipient.name} (${res.recipient.email})` : parsed.to;
41
- if (parsed.message) {
42
- console.log(`Sent ${parsed.filename} to ${who}.`);
43
- }
44
- else {
45
- console.log(`Shared ${parsed.filename} with ${who} (no message).`);
46
- }
47
- }
48
- catch (err) {
49
- (0, shared_1.recordAgentEvent)(config, {
50
- tool: "codiedev_send_to",
51
- ok: false,
52
- latencyMs: Date.now() - start,
53
- error: err.message,
54
- });
55
- const body = err.body;
56
- if (body && Array.isArray(body.candidates) && body.candidates.length > 0) {
57
- console.error(`Recipient "${parsed.to}" is ambiguous. Candidates:`);
58
- for (const c of body.candidates) {
59
- console.error(` - ${c.name} (${c.email})`);
60
- }
61
- }
62
- else {
63
- console.error(`Send failed: ${err.message}`);
64
- }
21
+ const pingArgs = sendToPingArgs(args);
22
+ if (!pingArgs) {
23
+ console.error('Usage: codiedev send <artifact> <recipient> ["<message>"] (same as: codiedev ping <recipient> ["<message>"] --with <artifact>)');
65
24
  process.exit(1);
66
25
  }
26
+ await (0, ping_1.runPing)(pingArgs, configOverride);
67
27
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // `codiedev share <filename> <recipient> [--role read|edit]` — silently
3
- // grant a teammate access to an artifact (no notification). Use
4
- // `codiedev send` instead when the user wants to actively loop someone in.
3
+ // grant a teammate access to an artifact (no notification). Kept as an alias:
4
+ // `codiedev ping <recipient> --with <filename> [--edit]` lands here too.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runShare = runShare;
7
7
  const shared_1 = require("./shared");
package/dist/hook.js CHANGED
@@ -64,6 +64,7 @@ const copilotHooks_1 = require("./copilotHooks");
64
64
  const piHooks_1 = require("./piHooks");
65
65
  const cliSession_1 = require("./cliSession");
66
66
  const utils_2 = require("./utils");
67
+ const promptGuard_1 = require("./promptGuard");
67
68
  // Pure builder for the discovery additionalContext. Returns null when there are
68
69
  // no matches. The suggested `codiedev pull` command carries the EXACT top key
69
70
  // (e.g. "pr-push.md") rather than a "<key>" placeholder — otherwise agents
@@ -597,6 +598,48 @@ async function main() {
597
598
  catch {
598
599
  process.exit(0);
599
600
  }
601
+ // Privacy exclusions suppress every Codiedev upload.
602
+ const captureIgnored = (0, captureExclusions_1.shouldIgnoreCaptureInput)((0, utils_1.readConfig)(), hookInput);
603
+ // Background refresh of the company's "stop prompts with a key" setting.
604
+ if (process.argv[2] === "prompt-guard-refresh") {
605
+ await (0, cliSession_1.ensureFreshToken)({ configPath: utils_2.CONFIG_PATH }).catch(() => undefined);
606
+ await (0, promptGuard_1.refreshPromptGuardPolicy)((0, utils_1.readConfig)());
607
+ process.exit(0);
608
+ }
609
+ // Stop a prompt with a key in it, when the company turned that on. Runs
610
+ // before discovery (which would send the prompt to the backend) and even
611
+ // where capture is excluded: it's local and never uploads the prompt.
612
+ const PROMPT_HOOKS = {
613
+ discover: "claude-code",
614
+ "discover-codex": "codex",
615
+ "discover-codie": "codie-harness",
616
+ "discover-copilot": "copilot",
617
+ "discover-pi": "pi",
618
+ "prompt-guard-cursor": "cursor",
619
+ };
620
+ const promptHarness = PROMPT_HOOKS[process.argv[2] ?? ""];
621
+ if (promptHarness) {
622
+ const guard = (0, promptGuard_1.checkPrompt)(hookInput.prompt, promptHarness, (0, promptGuard_1.readPromptGuardPolicy)());
623
+ // An ignored session makes no Codiedev request, not even this refresh.
624
+ if (!captureIgnored)
625
+ (0, promptGuard_1.maybeSpawnPromptGuardRefresh)({ connected: Boolean((0, utils_1.readConfig)()) });
626
+ if (guard.blocked) {
627
+ (0, captureLog_1.appendCaptureLog)({
628
+ stage: "prompt-guard",
629
+ sessionId: hookInput.session_id ?? "unknown",
630
+ outcome: "ok",
631
+ detail: `blocked harness=${promptHarness} rule=${guard.key.rule}`,
632
+ });
633
+ process.stdout.write(JSON.stringify(guard.response));
634
+ if (!captureIgnored)
635
+ await (0, promptGuard_1.reportPromptBlocked)((0, utils_1.readConfig)(), guard.key.rule, promptHarness);
636
+ process.exit(0);
637
+ }
638
+ if (promptHarness === "cursor") {
639
+ process.stdout.write(JSON.stringify({ continue: true }));
640
+ process.exit(0);
641
+ }
642
+ }
600
643
  // Local privacy boundary: every stdin-driven hook mode passes through this
601
644
  // check BEFORE prompt discovery, limit-event tails, SessionStart pings,
602
645
  // backup, self-heal, transcript reads, or detached dispatch. Ignored means
@@ -665,6 +708,8 @@ async function main() {
665
708
  (0, sync_1.maybeSpawnPackSync)({ connected: Boolean((0, utils_1.readConfig)()), cli: path.join(__dirname, "cli.js") });
666
709
  // Local session scrub of past sessions on this machine (hourly, detached).
667
710
  (0, localScrub_1.maybeSpawnLocalScrub)({ connected: Boolean((0, utils_1.readConfig)()), cwd: hookInput.cwd });
711
+ // Keep "stop prompts with a key" current before the first prompt.
712
+ (0, promptGuard_1.maybeSpawnPromptGuardRefresh)({ connected: Boolean((0, utils_1.readConfig)()) });
668
713
  const packNag = process.argv[2] === "session-start-opencode" || process.argv[2] === "session-start-pi" ? null : (0, sync_1.packNagLine)();
669
714
  // Claude Code adds SessionStart stdout to the session context.
670
715
  if (packNag && process.argv[2] === "session-start")
@@ -716,6 +761,8 @@ async function main() {
716
761
  }
717
762
  if (process.argv[2] === "capture-codex")
718
763
  (0, utils_1.ensureCodexLimitHookInstalled)();
764
+ if (/-opencode(?:-final)?$/.test(process.argv[2] ?? ""))
765
+ (0, utils_1.ensureOpenCodePluginCurrent)();
719
766
  const { session_id, transcript_path, cwd, workspace_roots } = hookInput;
720
767
  // Read here, in the process the harness spawned: the detached capture child
721
768
  // is reparented once this exits and can no longer see who launched it.
package/dist/mcp.js CHANGED
@@ -174,10 +174,10 @@ const TOOLS = [
174
174
  },
175
175
  {
176
176
  name: "codiedev_ping",
177
- description: "Send a message to a teammate on Codiedev, optionally attached to a " +
178
- "specific artifact. Use when the user asks to ping, ask, share with, " +
179
- "or get feedback from a teammate by name (e.g. 'ping a teammate about " +
180
- "this spec'). Recipients are resolved by first name, full name, or email " +
177
+ description: "Send a message to a teammate on Codiedev, optionally with an " +
178
+ "artifact (they also get read access to it). Use when the user asks to " +
179
+ "ping, ask, send something to, or get feedback from a teammate by name " +
180
+ "(e.g. 'send this spec to a teammate'). Recipients are resolved by first name, full name, or email " +
181
181
  "within the current org. They're notified by email and can reply " +
182
182
  "from their own agent.",
183
183
  inputSchema: {
@@ -194,8 +194,13 @@ const TOOLS = [
194
194
  },
195
195
  subjectKey: {
196
196
  type: "string",
197
- description: "Optional filename key of an artifact the message is about " +
198
- "(e.g. 'spec-214.md'). Leaving blank sends a standalone ping.",
197
+ description: "Optional filename key of an artifact to send (e.g. " +
198
+ "'spec-214.md'). The teammate also gets read access to it. " +
199
+ "Leaving blank sends a standalone ping.",
200
+ },
201
+ edit: {
202
+ type: "boolean",
203
+ description: "With subjectKey: give edit access instead of read (owner only).",
199
204
  },
200
205
  },
201
206
  required: ["to", "body"],
@@ -245,7 +250,7 @@ const TOOLS = [
245
250
  "without sending a notification. Use when the user says 'share this " +
246
251
  "with a teammate' or 'give someone access', and doesn't want to surface " +
247
252
  "it as a message. If they want the teammate actively notified, use " +
248
- "codiedev_send_to instead.",
253
+ "codiedev_ping with subjectKey instead.",
249
254
  inputSchema: {
250
255
  type: "object",
251
256
  properties: {
@@ -264,27 +269,6 @@ const TOOLS = [
264
269
  required: ["filename", "to"],
265
270
  },
266
271
  },
267
- {
268
- name: "codiedev_send_to",
269
- description: "Grant access to an artifact AND send a short message to a " +
270
- "teammate. Use when the user asks to 'send this to a teammate', 'ask " +
271
- "someone to look at this', or 'share this with a teammate and tell them X'. " +
272
- "This is the active handoff version of codiedev_share_with — the " +
273
- "recipient gets a ping in their inbox.",
274
- inputSchema: {
275
- type: "object",
276
- properties: {
277
- filename: { type: "string", description: "Artifact filename." },
278
- to: { type: "string", description: "Teammate first name, full name, or email." },
279
- message: {
280
- type: "string",
281
- description: "Optional short note to send with the share. If omitted, only " +
282
- "the grant is created (no ping).",
283
- },
284
- },
285
- required: ["filename", "to"],
286
- },
287
- },
288
272
  {
289
273
  name: "codiedev_search",
290
274
  description: "Search the team's artifact library by natural-language query. Use " +
@@ -534,12 +518,13 @@ async function handlePing(args, config) {
534
518
  const to = asString(args.to);
535
519
  const body = asString(args.body);
536
520
  const subjectKey = asStringOrUndefined(args.subjectKey);
521
+ const grant = subjectKey ? (args.edit === true ? "edit" : "read") : undefined;
537
522
  if (!to)
538
523
  throw new Error("to required");
539
524
  if (!body)
540
525
  throw new Error("body required");
541
526
  try {
542
- const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ping", { config, body: { to, body, subjectKey } });
527
+ const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ping", { config, body: { to, body, subjectKey, ...(grant ? { grant } : {}) } });
543
528
  return {
544
529
  content: [
545
530
  {
@@ -0,0 +1,63 @@
1
+ import { spawn as nodeSpawn } from "child_process";
2
+ import { type ExtraRule } from "./scrub/promptRules";
3
+ export type PromptHarness = "claude-code" | "codex" | "cursor" | "codie-harness" | "copilot" | "pi";
4
+ export interface TokenFormat {
5
+ name: string;
6
+ source: string;
7
+ keyword?: string;
8
+ }
9
+ export interface PromptGuardPolicy {
10
+ blockPrompts: boolean;
11
+ formats: TokenFormat[];
12
+ fetchedAt: number;
13
+ }
14
+ export declare const REFRESH_MS: number;
15
+ export interface PromptKey {
16
+ rule: string;
17
+ label: string;
18
+ hint: string;
19
+ }
20
+ export declare function keyLabel(rule: string): string;
21
+ /** The company's own token formats as scrub rules; a bad one is skipped. */
22
+ export declare function formatRules(formats: readonly TokenFormat[]): ExtraRule[];
23
+ /** The first real key in a prompt, or null. */
24
+ export declare function findPromptKey(prompt: string, formats?: readonly TokenFormat[], now?: number): PromptKey | null;
25
+ /** What the person sees. Names the kind of key and its last four, says
26
+ * nothing was sent, and how to do it instead. */
27
+ export declare function blockMessage(key: PromptKey): string;
28
+ /** The hook's stdout for each harness when it stops a prompt. */
29
+ export declare function blockResponse(harness: PromptHarness, message: string): Record<string, unknown>;
30
+ export declare function readPromptGuardPolicy(stateDir?: string): PromptGuardPolicy | null;
31
+ export declare function writePromptGuardPolicy(policy: PromptGuardPolicy, stateDir?: string): void;
32
+ interface GuardConfig {
33
+ token?: string;
34
+ backendUrl?: string;
35
+ }
36
+ /** Ask the server for the company's setting and cache it. On any failure the
37
+ * last cached answer stays (and its age makes the next hook try again). */
38
+ export declare function refreshPromptGuardPolicy(config: GuardConfig | null, fetchImpl?: typeof fetch, stateDir?: string, now?: number): Promise<PromptGuardPolicy | null>;
39
+ /** Start a background refresh when the cached setting is missing or stale.
40
+ * Stamps the cache's age first so a burst of hooks starts only one. */
41
+ export declare function maybeSpawnPromptGuardRefresh(opts: {
42
+ connected: boolean;
43
+ stateDir?: string;
44
+ now?: number;
45
+ hookEntry?: string;
46
+ spawn?: typeof nodeSpawn;
47
+ force?: boolean;
48
+ }): "spawned" | "fresh" | "disabled" | "error";
49
+ export interface PromptCheck {
50
+ blocked: false;
51
+ }
52
+ export interface PromptBlocked {
53
+ blocked: true;
54
+ key: PromptKey;
55
+ message: string;
56
+ response: Record<string, unknown>;
57
+ }
58
+ /** Check one prompt against the cached setting. Never throws. */
59
+ export declare function checkPrompt(prompt: string | undefined, harness: PromptHarness, policy: PromptGuardPolicy | null, now?: number): PromptCheck | PromptBlocked;
60
+ /** Tell the server a prompt was stopped: the rule and harness only. Detached
61
+ * and best-effort; a slow network never holds the prompt hook. */
62
+ export declare function reportPromptBlocked(config: GuardConfig | null, rule: string, harness: PromptHarness, fetchImpl?: typeof fetch): Promise<void>;
63
+ export {};