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.
- package/dist/captureLog.d.ts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +11 -8
- package/dist/codie.js +56 -4
- package/dist/codieLauncher.d.ts +3 -0
- package/dist/codieLauncher.js +16 -1
- package/dist/commands/ping.d.ts +10 -1
- package/dist/commands/ping.js +39 -10
- package/dist/commands/send.d.ts +1 -0
- package/dist/commands/send.js +15 -55
- package/dist/commands/share.js +2 -2
- package/dist/hook.js +47 -0
- package/dist/mcp.js +14 -29
- package/dist/promptGuard.d.ts +63 -0
- package/dist/promptGuard.js +261 -0
- package/dist/scrub/promptRules.d.ts +119 -0
- package/dist/scrub/promptRules.js +941 -0
- package/dist/utils.d.ts +5 -1
- package/dist/utils.js +52 -13
- package/package.json +1 -1
package/dist/captureLog.d.ts
CHANGED
|
@@ -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>"
|
|
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>"
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
package/dist/codieLauncher.d.ts
CHANGED
|
@@ -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;
|
package/dist/codieLauncher.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/commands/ping.d.ts
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
|
|
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>;
|
package/dist/commands/ping.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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(
|
|
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 } =
|
|
36
|
-
|
|
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: {
|
|
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",
|
package/dist/commands/send.d.ts
CHANGED
package/dist/commands/send.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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(" ")) :
|
|
18
|
-
return
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
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
|
}
|
package/dist/commands/share.js
CHANGED
|
@@ -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).
|
|
4
|
-
// `codiedev
|
|
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
|
|
178
|
-
"
|
|
179
|
-
"or get feedback from a teammate by name
|
|
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
|
|
198
|
-
"
|
|
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
|
-
"
|
|
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 {};
|