codiedev 0.9.83 → 0.9.85
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/cli.d.ts +1 -0
- package/dist/cli.js +36 -13
- package/dist/commands/inbox.js +9 -0
- package/dist/commands/pack.d.ts +5 -2
- package/dist/commands/pack.js +129 -10
- package/dist/commands/ping.js +11 -7
- package/dist/commands/pingFiles.d.ts +22 -0
- package/dist/commands/pingFiles.js +155 -0
- package/dist/commands/reply.d.ts +5 -0
- package/dist/commands/reply.js +54 -0
- package/dist/commands/shared.d.ts +11 -0
- package/dist/commands/shared.js +57 -0
- package/dist/hook.js +28 -8
- package/dist/inboxFormat.d.ts +10 -0
- package/dist/inboxFormat.js +18 -1
- package/dist/mcp.js +1 -48
- package/dist/packs/files.d.ts +46 -0
- package/dist/packs/files.js +307 -0
- package/dist/packs/harnesses.js +4 -3
- package/dist/packs/install.d.ts +4 -0
- package/dist/packs/install.js +35 -4
- package/dist/packs/manifest.d.ts +4 -0
- package/dist/packs/manifest.js +53 -5
- package/dist/packs/store.d.ts +13 -6
- package/dist/packs/store.js +23 -4
- package/dist/teamSkillContext.d.ts +1 -1
- package/dist/teamSkillContext.js +1 -1
- package/dist/unreadNotice.d.ts +22 -0
- package/dist/unreadNotice.js +81 -0
- package/dist/utils.js +5 -4
- package/package.json +1 -1
- package/dist/commands/ask.d.ts +0 -1
- package/dist/commands/ask.js +0 -58
package/dist/cli.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* codiedev ping <user> "<msg>" — send a message (optionally --with <key>)
|
|
10
10
|
* codiedev inbox — list pings (use --unread to filter)
|
|
11
11
|
* codiedev read <ping-id> — mark read + show full message
|
|
12
|
+
* codiedev reply <ping-id> "<msg>" — answer a ping in its thread
|
|
12
13
|
* codiedev note "<text>" — capture a thought
|
|
13
14
|
* codiedev help — show this help
|
|
14
15
|
*/
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* codiedev ping <user> "<msg>" — send a message (optionally --with <key>)
|
|
11
11
|
* codiedev inbox — list pings (use --unread to filter)
|
|
12
12
|
* codiedev read <ping-id> — mark read + show full message
|
|
13
|
+
* codiedev reply <ping-id> "<msg>" — answer a ping in its thread
|
|
13
14
|
* codiedev note "<text>" — capture a thought
|
|
14
15
|
* codiedev help — show this help
|
|
15
16
|
*/
|
|
@@ -53,8 +54,8 @@ const utils_1 = require("./utils");
|
|
|
53
54
|
const push_1 = require("./commands/push");
|
|
54
55
|
const delete_1 = require("./commands/delete");
|
|
55
56
|
const pull_1 = require("./commands/pull");
|
|
56
|
-
const ask_1 = require("./commands/ask");
|
|
57
57
|
const ping_1 = require("./commands/ping");
|
|
58
|
+
const reply_1 = require("./commands/reply");
|
|
58
59
|
const inbox_1 = require("./commands/inbox");
|
|
59
60
|
const note_1 = require("./commands/note");
|
|
60
61
|
const doctor_1 = require("./commands/doctor");
|
|
@@ -100,19 +101,17 @@ Artifacts:
|
|
|
100
101
|
codiedev delete <key> [-y] Delete every version of an artifact you
|
|
101
102
|
authored (clean up bad/test pushes).
|
|
102
103
|
Asks for confirmation; -y to skip.
|
|
103
|
-
codiedev ask "<question>" Ask a natural-language question of the
|
|
104
|
-
team's collective memory. Returns an
|
|
105
|
-
LLM-synthesized answer with citations
|
|
106
|
-
pointing at sessions + artifacts.
|
|
107
104
|
|
|
108
105
|
Messaging:
|
|
109
|
-
codiedev ping <user> "<msg>" [--with <key>]
|
|
106
|
+
codiedev ping <user> "<msg>" [--with <key>] [--file <path>]
|
|
110
107
|
Send a message to a teammate
|
|
111
108
|
codiedev send <key> <user> ["<msg>"] Share an artifact + ping in one step
|
|
112
109
|
codiedev share <key> <user> [--role read|edit]
|
|
113
110
|
Silently grant access (no notification)
|
|
114
111
|
codiedev inbox [--unread] [--limit N] Show messages
|
|
115
112
|
codiedev read <ping-id> Mark read + show full message
|
|
113
|
+
codiedev reply <ping-id> "<msg>" [--file <path>]
|
|
114
|
+
Answer a message in its thread
|
|
116
115
|
|
|
117
116
|
Starter packs:
|
|
118
117
|
codiedev pack list Packs assigned to your team
|
|
@@ -284,16 +283,36 @@ codiedev pull — fetch an artifact
|
|
|
284
283
|
codiedev ping — send a teammate a message, optionally attached to an artifact
|
|
285
284
|
|
|
286
285
|
Usage:
|
|
287
|
-
codiedev ping <user> "<message>" [--with <artifact-key>]
|
|
286
|
+
codiedev ping <user> "<message>" [--with <artifact-key>] [--file <path>]
|
|
288
287
|
|
|
289
288
|
<user> resolves to a teammate in your org by first name, full name,
|
|
290
289
|
or email. Ambiguous matches return a list — retry with the full email.
|
|
291
290
|
|
|
291
|
+
--file attaches a file (repeat for up to 10, 20 MB each). They see it
|
|
292
|
+
in their inbox and can open it in the portal.
|
|
293
|
+
|
|
292
294
|
Teammates are notified by email and can reply from their own agent.
|
|
295
|
+
To answer a message you received, use 'codiedev reply' instead: it
|
|
296
|
+
keeps the exchange in one thread.
|
|
293
297
|
|
|
294
298
|
Examples:
|
|
295
299
|
codiedev ping <teammate> "thoughts on batch vs parallel?" --with spec-cart-clear.md
|
|
296
300
|
codiedev ping <teammate>@example.com "can you take this one?"
|
|
301
|
+
codiedev ping <teammate> "shard timings from last night" --file ./timings.csv
|
|
302
|
+
`.trim(),
|
|
303
|
+
reply: `
|
|
304
|
+
codiedev reply — answer a message in its thread
|
|
305
|
+
|
|
306
|
+
Usage:
|
|
307
|
+
codiedev reply <ping-id> "<message>" [--file <path>]
|
|
308
|
+
|
|
309
|
+
Grab the ping-id from 'codiedev inbox'. The reply goes to whoever sent
|
|
310
|
+
that ping, stays in the same conversation, and marks their message
|
|
311
|
+
read and answered, so it stops showing as waiting on you.
|
|
312
|
+
|
|
313
|
+
Examples:
|
|
314
|
+
codiedev reply <ping-id> "yes, step 2 covers partial refunds"
|
|
315
|
+
codiedev reply <ping-id> "updated numbers attached" --file ./timings.csv
|
|
297
316
|
`.trim(),
|
|
298
317
|
inbox: `
|
|
299
318
|
codiedev inbox — show messages from teammates
|
|
@@ -415,13 +434,12 @@ signal. The stored artifact type still follows the filename rule
|
|
|
415
434
|
|
|
416
435
|
## Portal
|
|
417
436
|
|
|
418
|
-
|
|
437
|
+
Messages you send and receive are also at:
|
|
419
438
|
|
|
420
439
|
https://codiedev.com/portal/inbox
|
|
421
440
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
email notifications when pinged.
|
|
441
|
+
A teammate can read and answer there without touching a terminal, and
|
|
442
|
+
gets an email when pinged.
|
|
425
443
|
|
|
426
444
|
## Agent-native
|
|
427
445
|
|
|
@@ -564,8 +582,10 @@ async function main() {
|
|
|
564
582
|
await (0, pull_1.runPull)(rest);
|
|
565
583
|
return;
|
|
566
584
|
case "ask":
|
|
567
|
-
|
|
568
|
-
|
|
585
|
+
// Removed 2026-11. Kept as a named case so the answer is a pointer,
|
|
586
|
+
// not "unknown command".
|
|
587
|
+
console.error('codiedev ask has been removed. Use: codiedev search "<query>"');
|
|
588
|
+
process.exit(1);
|
|
569
589
|
case "ping":
|
|
570
590
|
await (0, ping_1.runPing)(rest);
|
|
571
591
|
return;
|
|
@@ -582,6 +602,9 @@ async function main() {
|
|
|
582
602
|
case "read":
|
|
583
603
|
await (0, inbox_1.runRead)(rest);
|
|
584
604
|
return;
|
|
605
|
+
case "reply":
|
|
606
|
+
await (0, reply_1.runReply)(rest);
|
|
607
|
+
return;
|
|
585
608
|
case "scrub": {
|
|
586
609
|
const { runScrubCommand } = await Promise.resolve().then(() => __importStar(require("./commands/scrub")));
|
|
587
610
|
process.exitCode = await runScrubCommand(rest);
|
package/dist/commands/inbox.js
CHANGED
|
@@ -107,7 +107,16 @@ async function runRead(args) {
|
|
|
107
107
|
console.log();
|
|
108
108
|
console.log(ping.body || "(empty)");
|
|
109
109
|
console.log();
|
|
110
|
+
if (ping.attachments?.length) {
|
|
111
|
+
console.log(ping.attachments.length === 1 ? "File:" : "Files:");
|
|
112
|
+
for (const a of ping.attachments) {
|
|
113
|
+
console.log(` ${(0, inboxFormat_1.fileList)([a])}`);
|
|
114
|
+
console.log(` ${a.url ?? "(no longer stored)"}`);
|
|
115
|
+
}
|
|
116
|
+
console.log();
|
|
117
|
+
}
|
|
110
118
|
console.log("✓ Marked as read");
|
|
119
|
+
console.log(`Reply: codiedev reply ${ping.pingId} "<message>"`);
|
|
111
120
|
if (ping.subjectKey) {
|
|
112
121
|
console.log(`Pull: codiedev pull ${ping.subjectKey}`);
|
|
113
122
|
}
|
package/dist/commands/pack.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { CodiedevConfig } from "../utils";
|
|
|
2
2
|
import { InstallOptions, PackInstallResult, PackUninstallResult } from "../packs/install";
|
|
3
3
|
import { HarnessName } from "../packs/harnesses";
|
|
4
4
|
import { PackManifest } from "../packs/manifest";
|
|
5
|
+
import { PackBundleFile } from "../packs/files";
|
|
5
6
|
export declare const DEFAULT_BACKEND_URL = "https://cli.codiedev.com";
|
|
6
7
|
/** CODIEDEV_TOKEN alone is enough: no ~/.codiedev/config.json needed (CI).
|
|
7
8
|
* CODIEDEV_URL defaults to production. Otherwise the connected config. */
|
|
@@ -22,6 +23,8 @@ export interface PackArgs {
|
|
|
22
23
|
manifest?: boolean;
|
|
23
24
|
}
|
|
24
25
|
export declare function parsePackArgs(args: string[]): PackArgs;
|
|
26
|
+
/** Download a pack's files (SIG-127) into the blob cache, verified by sha256. */
|
|
27
|
+
export declare function downloadPackFiles(config: CodiedevConfig, slug: string, files: PackBundleFile[], dir: string): Promise<number>;
|
|
25
28
|
export declare function runList(config: CodiedevConfig, json: boolean, overrides?: Partial<InstallOptions>): Promise<void>;
|
|
26
29
|
export interface InstallRun {
|
|
27
30
|
installed: PackInstallResult[];
|
|
@@ -45,10 +48,10 @@ export declare function commitExport(dir: string, message: string): string;
|
|
|
45
48
|
/** Write a CI-committable copy of the pack: `<out>/plugins/<slug>/…` plus
|
|
46
49
|
* `<out>/.claude-plugin/marketplace.json` (existing entries for other packs
|
|
47
50
|
* in the same folder are kept). */
|
|
48
|
-
export declare function exportPluginFolder(out: string, manifest: PackManifest): string;
|
|
51
|
+
export declare function exportPluginFolder(out: string, manifest: PackManifest, blobDir?: string): string;
|
|
49
52
|
export declare function runExport(config: CodiedevConfig, args: PackArgs): Promise<{
|
|
50
53
|
written: string;
|
|
51
54
|
commit?: string;
|
|
52
55
|
}>;
|
|
53
56
|
export declare function runApply(config: CodiedevConfig, args: PackArgs): Promise<Record<string, unknown>>;
|
|
54
|
-
export declare const PACK_HELP = "Usage:\n codiedev pack list Packs assigned to your team\n codiedev pack install [slug] [--harness claude,codex,copilot,cursor,codie,pi] [--dry-run] [--json] [--quiet]\n Install one pack, or every pack that applies\n codiedev pack status [--json] Installed packs and whether they're up to date\n codiedev pack export <slug> --out <dir> [--git] [--manifest]\n Write the pack as a Claude-format plugin folder plus\n .claude-plugin/marketplace.json (commit it for CI).\n --git inits the folder and commits; --manifest writes\n an editable <slug>.pack.md instead\n codiedev pack apply <pack.md>
|
|
57
|
+
export declare const PACK_HELP = "Usage:\n codiedev pack list Packs assigned to your team\n codiedev pack install [slug] [--harness claude,codex,copilot,cursor,codie,pi] [--dry-run] [--json] [--quiet]\n Install one pack, or every pack that applies\n codiedev pack status [--json] Installed packs and whether they're up to date\n codiedev pack export <slug> --out <dir> [--git] [--manifest]\n Write the pack as a Claude-format plugin folder plus\n .claude-plugin/marketplace.json (commit it for CI).\n --git inits the folder and commits; --manifest writes\n an editable <slug>.pack.md instead\n codiedev pack apply <pack.md> [--dry-run]\n Create or update a pack from a pack.md\n\nFiles (scripts, PDFs, a runner folder): list them under \"## Files\" in pack.md,\none \"- <path in pack> <- <local path>\" per line (local paths are relative to\nthe pack.md; \".\" is the pack root):\n\n ## Files\n\n - runner <- ~/.claude/runner\n - skills/healthcare-scan <- ~/.claude/skills/healthcare-scan\n\n`pack apply` uploads them (admins and team managers; 15MB per file, 50MB per\npack; caches, .git and hidden files are skipped). Without a \"## Files\"\nsection the pack's files are left alone. In skills, point at files relative to\nthe skill's folder, or use ${CLAUDE_PLUGIN_ROOT}/runner/...: Codiedev swaps in the\nreal path for Codex, Copilot, Cursor, pi and Codie.\n\nCursor: packs are copied to ~/.cursor/plugins/local/codiedev-<slug>/. A Cursor\nteam admin can disable \"user local plugin imports\"; then Cursor ignores them.\n\nAuto-update: session starts run `codiedev pack install --quiet` in the background\nabout once an hour. Opt out with CODIEDEV_PACK_AUTOSYNC=0.\n\nHooks for specific skills only: codiedev hook-gate --skills a,b --cmd-b64 <base64url of the command>\n(the pack editor writes this; the command runs via /bin/sh, or cmd.exe on Windows)\n\nCI: set CODIEDEV_TOKEN to a pack CI key (cdvci_\u2026, from the pack's page in the portal).\nNo `codiedev connect` or config file is needed. CI mode (a cdvci_ key, or CI=true)\nnever auto-syncs, reports or prunes, installs the one pack the key can see, defaults\n--harness to claude when it's on PATH, and exits non-zero unless every requested tool\ngot the pack. Skills from a pack are /<pack-slug>:<skill> in Claude Code.\n\n GitLab CI (.gitlab-ci.yml; add CODIEDEV_TOKEN and ANTHROPIC_API_KEY as masked\n CI/CD variables):\n pack-scan:\n image: node:22\n script:\n - npm i -g codiedev @anthropic-ai/claude-code\n - codiedev pack install --harness claude\n - claude -p \"/platform-starter:healthcare-scan\" --output-format text\n\n GitHub Actions (repository secrets CODIEDEV_TOKEN and ANTHROPIC_API_KEY):\n - uses: actions/setup-node@v4\n with: { node-version: 22 }\n - run: npm i -g codiedev @anthropic-ai/claude-code\n - run: codiedev pack install --harness claude\n env:\n CODIEDEV_TOKEN: ${{ secrets.CODIEDEV_TOKEN }}\n - run: claude -p \"/platform-starter:healthcare-scan\" --output-format text\n env:\n ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n\n Or vendor the plugin into the repo instead of installing at build time:\n codiedev pack export <slug> --out .codiedev/packs";
|
package/dist/commands/pack.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.PACK_HELP = exports.DEFAULT_BACKEND_URL = void 0;
|
|
|
37
37
|
exports.resolvePackConfig = resolvePackConfig;
|
|
38
38
|
exports.ciHarnesses = ciHarnesses;
|
|
39
39
|
exports.parsePackArgs = parsePackArgs;
|
|
40
|
+
exports.downloadPackFiles = downloadPackFiles;
|
|
40
41
|
exports.runList = runList;
|
|
41
42
|
exports.runInstall = runInstall;
|
|
42
43
|
exports.runStatus = runStatus;
|
|
@@ -59,6 +60,7 @@ const harnesses_2 = require("../packs/harnesses");
|
|
|
59
60
|
const os = __importStar(require("os"));
|
|
60
61
|
const utils_1 = require("../utils");
|
|
61
62
|
const shared_1 = require("./shared");
|
|
63
|
+
const files_1 = require("../packs/files");
|
|
62
64
|
// --- CI (pack-scoped `cdvci_` keys) ------------------------------------------
|
|
63
65
|
exports.DEFAULT_BACKEND_URL = "https://cli.codiedev.com";
|
|
64
66
|
/** CODIEDEV_TOKEN alone is enough: no ~/.codiedev/config.json needed (CI).
|
|
@@ -101,7 +103,23 @@ const USAGE = `Usage:
|
|
|
101
103
|
.claude-plugin/marketplace.json (commit it for CI).
|
|
102
104
|
--git inits the folder and commits; --manifest writes
|
|
103
105
|
an editable <slug>.pack.md instead
|
|
104
|
-
codiedev pack apply <pack.md>
|
|
106
|
+
codiedev pack apply <pack.md> [--dry-run]
|
|
107
|
+
Create or update a pack from a pack.md
|
|
108
|
+
|
|
109
|
+
Files (scripts, PDFs, a runner folder): list them under "## Files" in pack.md,
|
|
110
|
+
one "- <path in pack> <- <local path>" per line (local paths are relative to
|
|
111
|
+
the pack.md; "." is the pack root):
|
|
112
|
+
|
|
113
|
+
## Files
|
|
114
|
+
|
|
115
|
+
- runner <- ~/.claude/runner
|
|
116
|
+
- skills/healthcare-scan <- ~/.claude/skills/healthcare-scan
|
|
117
|
+
|
|
118
|
+
\`pack apply\` uploads them (admins and team managers; 15MB per file, 50MB per
|
|
119
|
+
pack; caches, .git and hidden files are skipped). Without a "## Files"
|
|
120
|
+
section the pack's files are left alone. In skills, point at files relative to
|
|
121
|
+
the skill's folder, or use \${CLAUDE_PLUGIN_ROOT}/runner/...: Codiedev swaps in the
|
|
122
|
+
real path for Codex, Copilot, Cursor, pi and Codie.
|
|
105
123
|
|
|
106
124
|
Cursor: packs are copied to ~/.cursor/plugins/local/codiedev-<slug>/. A Cursor
|
|
107
125
|
team admin can disable "user local plugin imports"; then Cursor ignores them.
|
|
@@ -188,6 +206,16 @@ async function fetchManifest(config, slug) {
|
|
|
188
206
|
const raw = await (0, shared_1.apiRequest)("GET", "/api/cli/packs/manifest", { config, query: { slug } });
|
|
189
207
|
return (0, manifest_1.parseManifest)(raw);
|
|
190
208
|
}
|
|
209
|
+
/** Download a pack's files (SIG-127) into the blob cache, verified by sha256. */
|
|
210
|
+
async function downloadPackFiles(config, slug, files, dir) {
|
|
211
|
+
if (!files.length)
|
|
212
|
+
return 0;
|
|
213
|
+
const { downloaded } = await (0, files_1.ensureBlobs)(dir, files, (file) => (0, shared_1.apiRequestBytes)("GET", "/api/cli/packs/file", { config, query: { slug, sha256: file.sha256 }, maxBytes: files_1.PACK_FILE_MAX_BYTES + 1 })
|
|
214
|
+
.catch((error) => {
|
|
215
|
+
throw new Error(`couldn't download ${file.path} (${error.message})`);
|
|
216
|
+
}));
|
|
217
|
+
return downloaded;
|
|
218
|
+
}
|
|
191
219
|
async function runList(config, json, overrides = {}) {
|
|
192
220
|
const packs = await listPacks(config);
|
|
193
221
|
const registry = (0, store_1.readRegistry)((0, paths_1.packsRoot)(overrides.env, overrides.home));
|
|
@@ -206,6 +234,7 @@ async function runList(config, json, overrides = {}) {
|
|
|
206
234
|
`${p.itemCount ?? 0} skill${p.itemCount === 1 ? "" : "s"}`,
|
|
207
235
|
`${p.agentCount ?? 0} agent${p.agentCount === 1 ? "" : "s"}`,
|
|
208
236
|
...(p.hasHooks ? ["hooks"] : []),
|
|
237
|
+
...(p.fileCount ? [`${p.fileCount} file${p.fileCount === 1 ? "" : "s"}`] : []),
|
|
209
238
|
].join(", ");
|
|
210
239
|
const channel = (0, manifest_2.cleanLabel)(p.channel, 16);
|
|
211
240
|
console.log(`${p.slug} v${p.version}${channel ? ` [${channel}]` : ""} ${(0, manifest_2.displayName)(p)} (${counts}) — ${state}`);
|
|
@@ -244,8 +273,27 @@ async function runInstall(config, args, overrides = {}) {
|
|
|
244
273
|
// A listed pack that 404s a moment later (race with an unassign) is skipped.
|
|
245
274
|
}
|
|
246
275
|
}
|
|
247
|
-
|
|
248
|
-
|
|
276
|
+
// Pack files first: a pack whose files can't be fetched keeps its previous
|
|
277
|
+
// install untouched rather than shipping half its folders.
|
|
278
|
+
const blobDir = overrides.blobDir ?? (0, files_1.blobsDir)(root);
|
|
279
|
+
const ready = [];
|
|
280
|
+
let downloadFailed = false;
|
|
281
|
+
for (const manifest of manifests) {
|
|
282
|
+
if (args.dryRun || !manifest.files?.length) {
|
|
283
|
+
ready.push(manifest);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
await downloadPackFiles(config, manifest.slug, manifest.files, blobDir);
|
|
288
|
+
ready.push(manifest);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
downloadFailed = true;
|
|
292
|
+
console.error(`✗ ${manifest.slug}: ${error.message}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
let installed = ready.length
|
|
296
|
+
? (0, install_1.installPacks)({ ...overrides, manifests: ready, harnesses, dryRun: args.dryRun, blobDir })
|
|
249
297
|
: [];
|
|
250
298
|
let removed = [];
|
|
251
299
|
if (!args.dryRun && !ci) {
|
|
@@ -293,7 +341,7 @@ async function runInstall(config, args, overrides = {}) {
|
|
|
293
341
|
const ciFailed = ci && !args.dryRun && (installed.length === 0 || installed.some((p) => p.results.some((r) => r.status !== "ok")));
|
|
294
342
|
if (ci && !args.dryRun && installed.length === 0)
|
|
295
343
|
console.error("No pack is visible to this key.");
|
|
296
|
-
if (failed || ciFailed || (args.slug && removed.length + revoked.length > 0))
|
|
344
|
+
if (failed || ciFailed || downloadFailed || (args.slug && removed.length + revoked.length > 0))
|
|
297
345
|
process.exitCode = 1;
|
|
298
346
|
return run;
|
|
299
347
|
}
|
|
@@ -494,9 +542,9 @@ function readMarketplaceEntries(dir) {
|
|
|
494
542
|
/** Write a CI-committable copy of the pack: `<out>/plugins/<slug>/…` plus
|
|
495
543
|
* `<out>/.claude-plugin/marketplace.json` (existing entries for other packs
|
|
496
544
|
* in the same folder are kept). */
|
|
497
|
-
function exportPluginFolder(out, manifest) {
|
|
545
|
+
function exportPluginFolder(out, manifest, blobDir) {
|
|
498
546
|
fs.mkdirSync(out, { recursive: true });
|
|
499
|
-
const dir = (0, store_1.writePluginDir)(out, manifest);
|
|
547
|
+
const dir = (0, store_1.writePluginDir)(out, manifest, { blobDir });
|
|
500
548
|
const entries = readMarketplaceEntries(out).filter((e) => e.slug !== manifest.slug);
|
|
501
549
|
entries.push({ slug: manifest.slug, description: manifest.description || manifest.name, version: manifest.version });
|
|
502
550
|
(0, store_1.writeMarketplace)(out, entries);
|
|
@@ -524,7 +572,24 @@ async function runExport(config, args) {
|
|
|
524
572
|
fs.mkdirSync(out, { recursive: true });
|
|
525
573
|
// <slug>.pack.md so several packs can share one folder.
|
|
526
574
|
written = path.join(out, `${args.slug}.pack.md`);
|
|
527
|
-
|
|
575
|
+
let markdown = res.markdown.endsWith("\n") ? res.markdown : `${res.markdown}\n`;
|
|
576
|
+
// Pack files go beside it in <slug>.files/, mapped back with `## Files`
|
|
577
|
+
// so `pack apply` round-trips them.
|
|
578
|
+
const files = (0, manifest_1.parseManifest)({ slug: args.slug, version: 0, contentHash: "-", files: res.files }).files ?? [];
|
|
579
|
+
if (files.length) {
|
|
580
|
+
const blobDir = (0, files_1.blobsDir)((0, paths_1.packsRoot)());
|
|
581
|
+
await downloadPackFiles(config, args.slug, files, blobDir);
|
|
582
|
+
const filesDir = path.join(out, `${args.slug}.files`);
|
|
583
|
+
fs.rmSync(filesDir, { recursive: true, force: true });
|
|
584
|
+
for (const f of files) {
|
|
585
|
+
const target = path.join(filesDir, ...f.path.split("/"));
|
|
586
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
587
|
+
fs.copyFileSync((0, files_1.blobPath)(blobDir, f.sha256), target);
|
|
588
|
+
fs.chmodSync(target, f.executable ? 0o755 : 0o644);
|
|
589
|
+
}
|
|
590
|
+
markdown = `${markdown.trimEnd()}\n\n## Files\n\n- . <- ./${args.slug}.files\n`;
|
|
591
|
+
}
|
|
592
|
+
(0, store_1.writeFileAtomic)(written, markdown);
|
|
528
593
|
message = `codiedev pack export ${args.slug} (${args.slug}.pack.md)`;
|
|
529
594
|
}
|
|
530
595
|
else {
|
|
@@ -539,7 +604,9 @@ async function runExport(config, args) {
|
|
|
539
604
|
throw new Error(`Refusing to export ${args.slug}: ${error.message}`);
|
|
540
605
|
throw error;
|
|
541
606
|
}
|
|
542
|
-
|
|
607
|
+
const blobDir = (0, files_1.blobsDir)((0, paths_1.packsRoot)());
|
|
608
|
+
await downloadPackFiles(config, manifest.slug, manifest.files ?? [], blobDir);
|
|
609
|
+
written = exportPluginFolder(out, manifest, blobDir);
|
|
543
610
|
message = `codiedev pack export ${manifest.slug} v${manifest.version}`;
|
|
544
611
|
}
|
|
545
612
|
const commit = args.git ? commitExport(out, message) : undefined;
|
|
@@ -567,13 +634,31 @@ async function runApply(config, args) {
|
|
|
567
634
|
}
|
|
568
635
|
if (!markdown.trim())
|
|
569
636
|
throw new Error(`${file} is empty`);
|
|
637
|
+
// `## Files` (SIG-127): local folders/files to ship in the plugin dir.
|
|
638
|
+
// Absent ⇒ the pack's files are left as they are.
|
|
639
|
+
const entries = (0, files_1.parseFilesSection)(markdown);
|
|
640
|
+
const local = entries ? (0, files_1.collectLocalFiles)(entries, path.dirname(file)) : null;
|
|
641
|
+
for (const note of local?.notes ?? [])
|
|
642
|
+
console.log(` · ${note}`);
|
|
570
643
|
if (args.dryRun) {
|
|
571
|
-
|
|
644
|
+
const extra = local ? `, ${local.files.length} file${local.files.length === 1 ? "" : "s"} (${formatBytes(local.files.reduce((n, f) => n + f.size, 0))})` : "";
|
|
645
|
+
console.log(`Dry run — would apply ${file} (${Buffer.byteLength(markdown)} bytes${extra}).`);
|
|
646
|
+
for (const f of local?.files ?? [])
|
|
647
|
+
console.log(` ${f.path}`);
|
|
572
648
|
return {};
|
|
573
649
|
}
|
|
650
|
+
let uploaded = 0;
|
|
651
|
+
if (local?.files.length)
|
|
652
|
+
uploaded = await uploadPackFiles(config, local.files);
|
|
574
653
|
let res;
|
|
575
654
|
try {
|
|
576
|
-
res = await (0, shared_1.apiRequest)("POST", "/api/cli/packs/apply", {
|
|
655
|
+
res = await (0, shared_1.apiRequest)("POST", "/api/cli/packs/apply", {
|
|
656
|
+
config,
|
|
657
|
+
body: {
|
|
658
|
+
markdown,
|
|
659
|
+
...(local ? { files: local.files.map((f) => ({ path: f.path, sha256: f.sha256, ...(f.executable ? { executable: true } : {}) })) } : {}),
|
|
660
|
+
},
|
|
661
|
+
});
|
|
577
662
|
}
|
|
578
663
|
catch (error) {
|
|
579
664
|
// The server reports pack.md problems with line numbers; show them as-is.
|
|
@@ -590,6 +675,8 @@ async function runApply(config, args) {
|
|
|
590
675
|
const verb = res.created === true ? "Created" : res.changed === false ? "No changes to" : "Updated";
|
|
591
676
|
const version = typeof res.version === "number" ? ` v${res.version}` : "";
|
|
592
677
|
console.log(`✓ ${verb} ${slug}${version} from ${path.basename(file)}`);
|
|
678
|
+
if (local)
|
|
679
|
+
console.log(` files: ${local.files.length} (${uploaded} uploaded, ${local.files.length - uploaded} already on Codiedev)`);
|
|
593
680
|
if (Array.isArray(res.warnings)) {
|
|
594
681
|
for (const w of res.warnings) {
|
|
595
682
|
const text = typeof w === "string" ? w : typeof w?.message === "string" ? `${w.agent ? `${w.agent}: ` : ""}${w.message}` : JSON.stringify(w);
|
|
@@ -599,4 +686,36 @@ async function runApply(config, args) {
|
|
|
599
686
|
}
|
|
600
687
|
return res;
|
|
601
688
|
}
|
|
689
|
+
function formatBytes(n) {
|
|
690
|
+
return n >= 1024 * 1024 ? `${(n / (1024 * 1024)).toFixed(1)}MB` : n >= 1024 ? `${Math.round(n / 1024)}KB` : `${n}B`;
|
|
691
|
+
}
|
|
692
|
+
/** Upload the blobs Codiedev doesn't have yet. Returns how many were sent. */
|
|
693
|
+
async function uploadPackFiles(config, files) {
|
|
694
|
+
const unique = [...new Map(files.map((f) => [f.sha256, f])).values()];
|
|
695
|
+
let known;
|
|
696
|
+
try {
|
|
697
|
+
known = (await (0, shared_1.apiRequest)("POST", "/api/cli/packs/files/known", { config, body: { sha256s: unique.map((f) => f.sha256) } })).known ?? [];
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
const e = error;
|
|
701
|
+
if (e.status === 403)
|
|
702
|
+
throw new Error(`Only admins and team managers can add files to a pack (${e.message})`);
|
|
703
|
+
throw error;
|
|
704
|
+
}
|
|
705
|
+
const have = new Set(known);
|
|
706
|
+
let sent = 0;
|
|
707
|
+
for (const f of unique) {
|
|
708
|
+
if (have.has(f.sha256))
|
|
709
|
+
continue;
|
|
710
|
+
const body = fs.readFileSync(f.abs);
|
|
711
|
+
try {
|
|
712
|
+
await (0, shared_1.apiRequestBytes)("POST", "/api/cli/packs/files/upload", { config, body, query: { sha256: f.sha256 } });
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
throw new Error(`couldn't upload ${f.path} (${error.message})`);
|
|
716
|
+
}
|
|
717
|
+
sent += 1;
|
|
718
|
+
}
|
|
719
|
+
return sent;
|
|
720
|
+
}
|
|
602
721
|
exports.PACK_HELP = USAGE;
|
package/dist/commands/ping.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runPing = runPing;
|
|
4
4
|
const shared_1 = require("./shared");
|
|
5
|
-
|
|
5
|
+
const pingFiles_1 = require("./pingFiles");
|
|
6
|
+
function parseArgs(allArgs) {
|
|
7
|
+
const { files, rest: args } = (0, pingFiles_1.takeFileArgs)(allArgs);
|
|
6
8
|
let to;
|
|
7
9
|
const positional = [];
|
|
8
10
|
let subjectKey;
|
|
@@ -18,31 +20,33 @@ function parseArgs(args) {
|
|
|
18
20
|
positional.push(a);
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
|
|
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>]");
|
|
23
26
|
process.exit(1);
|
|
24
27
|
}
|
|
25
28
|
to = positional[0];
|
|
26
29
|
// Join remaining positionals in case the shell split the message on spaces
|
|
27
30
|
// despite quoting (happens with some escape sequences).
|
|
28
31
|
const body = (0, shared_1.stripQuotes)(positional.slice(1).join(" "));
|
|
29
|
-
return { to, body, subjectKey };
|
|
32
|
+
return { to, body: body || (files.length ? "Sent a file." : ""), subjectKey, files };
|
|
30
33
|
}
|
|
31
34
|
async function runPing(args) {
|
|
32
|
-
const { to, body, subjectKey } = parseArgs(args);
|
|
35
|
+
const { to, body, subjectKey, files } = parseArgs(args);
|
|
33
36
|
const config = (0, shared_1.requireConfig)();
|
|
34
37
|
const start = Date.now();
|
|
35
38
|
try {
|
|
39
|
+
const attachmentIds = await (0, pingFiles_1.uploadOrExit)(config, files);
|
|
36
40
|
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ping", {
|
|
37
41
|
config,
|
|
38
|
-
body: { to, body, subjectKey },
|
|
42
|
+
body: { to, body, subjectKey, ...(attachmentIds.length ? { attachmentIds } : {}) },
|
|
39
43
|
});
|
|
40
44
|
(0, shared_1.recordAgentEvent)(config, {
|
|
41
45
|
tool: "codiedev_ping",
|
|
42
46
|
ok: true,
|
|
43
47
|
latencyMs: Date.now() - start,
|
|
44
48
|
});
|
|
45
|
-
console.log(`✓ Pinged ${res.recipient.name} <${res.recipient.email}>${subjectKey ? ` on ${subjectKey}` : ""}`);
|
|
49
|
+
console.log(`✓ Pinged ${res.recipient.name} <${res.recipient.email}>${subjectKey ? ` on ${subjectKey}` : ""}${files.length ? ` with ${files.length} file${files.length === 1 ? "" : "s"}` : ""}`);
|
|
46
50
|
}
|
|
47
51
|
catch (err) {
|
|
48
52
|
const e = err;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CodiedevConfig } from "../utils";
|
|
2
|
+
export declare const MAX_PING_FILES = 10;
|
|
3
|
+
export declare const MAX_PING_FILE_BYTES: number;
|
|
4
|
+
export declare function contentTypeFor(fileName: string): string;
|
|
5
|
+
export declare function formatBytes(bytes: number): string;
|
|
6
|
+
/** Pull every `--file <path>` / `-f <path>` / `--file=<path>` out of argv,
|
|
7
|
+
* returning the paths and the remaining args untouched. */
|
|
8
|
+
export declare function takeFileArgs(args: string[]): {
|
|
9
|
+
files: string[];
|
|
10
|
+
rest: string[];
|
|
11
|
+
};
|
|
12
|
+
export type FileProblem = {
|
|
13
|
+
path: string;
|
|
14
|
+
reason: string;
|
|
15
|
+
};
|
|
16
|
+
/** Check every path before uploading any, so one bad path can't leave the
|
|
17
|
+
* others uploaded and unsent. */
|
|
18
|
+
export declare function checkFiles(paths: string[]): FileProblem[];
|
|
19
|
+
/** Upload in order; the returned ids go in the ping's `attachmentIds`. */
|
|
20
|
+
export declare function uploadPingFiles(config: CodiedevConfig, paths: string[]): Promise<string[]>;
|
|
21
|
+
/** Validate, then upload. Prints the problems and exits on a bad path. */
|
|
22
|
+
export declare function uploadOrExit(config: CodiedevConfig, paths: string[]): Promise<string[]>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MAX_PING_FILE_BYTES = exports.MAX_PING_FILES = void 0;
|
|
7
|
+
exports.contentTypeFor = contentTypeFor;
|
|
8
|
+
exports.formatBytes = formatBytes;
|
|
9
|
+
exports.takeFileArgs = takeFileArgs;
|
|
10
|
+
exports.checkFiles = checkFiles;
|
|
11
|
+
exports.uploadPingFiles = uploadPingFiles;
|
|
12
|
+
exports.uploadOrExit = uploadOrExit;
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const http_1 = __importDefault(require("http"));
|
|
15
|
+
const https_1 = __importDefault(require("https"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const version_1 = require("../version");
|
|
18
|
+
// Files sent with `codiedev ping --file` / `codiedev reply --file`. The server
|
|
19
|
+
// stores each one and hands back an id; the ping then names those ids. Limits
|
|
20
|
+
// mirror the server's so a too-large file fails here, before the upload.
|
|
21
|
+
exports.MAX_PING_FILES = 10;
|
|
22
|
+
exports.MAX_PING_FILE_BYTES = 20 * 1024 * 1024;
|
|
23
|
+
const CONTENT_TYPES = {
|
|
24
|
+
csv: "text/csv",
|
|
25
|
+
tsv: "text/tab-separated-values",
|
|
26
|
+
txt: "text/plain",
|
|
27
|
+
log: "text/plain",
|
|
28
|
+
md: "text/markdown",
|
|
29
|
+
json: "application/json",
|
|
30
|
+
yaml: "text/plain",
|
|
31
|
+
yml: "text/plain",
|
|
32
|
+
diff: "text/plain",
|
|
33
|
+
patch: "text/plain",
|
|
34
|
+
png: "image/png",
|
|
35
|
+
jpg: "image/jpeg",
|
|
36
|
+
jpeg: "image/jpeg",
|
|
37
|
+
gif: "image/gif",
|
|
38
|
+
webp: "image/webp",
|
|
39
|
+
pdf: "application/pdf",
|
|
40
|
+
zip: "application/zip",
|
|
41
|
+
};
|
|
42
|
+
function contentTypeFor(fileName) {
|
|
43
|
+
const ext = path_1.default.extname(fileName).slice(1).toLowerCase();
|
|
44
|
+
return CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
45
|
+
}
|
|
46
|
+
function formatBytes(bytes) {
|
|
47
|
+
if (bytes < 1024)
|
|
48
|
+
return `${bytes} B`;
|
|
49
|
+
if (bytes < 1024 * 1024)
|
|
50
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
51
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
52
|
+
}
|
|
53
|
+
/** Pull every `--file <path>` / `-f <path>` / `--file=<path>` out of argv,
|
|
54
|
+
* returning the paths and the remaining args untouched. */
|
|
55
|
+
function takeFileArgs(args) {
|
|
56
|
+
const files = [];
|
|
57
|
+
const rest = [];
|
|
58
|
+
for (let i = 0; i < args.length; i++) {
|
|
59
|
+
const a = args[i];
|
|
60
|
+
if ((a === "--file" || a === "-f") && i + 1 < args.length)
|
|
61
|
+
files.push(args[++i]);
|
|
62
|
+
else if (a.startsWith("--file="))
|
|
63
|
+
files.push(a.slice("--file=".length));
|
|
64
|
+
else
|
|
65
|
+
rest.push(a);
|
|
66
|
+
}
|
|
67
|
+
return { files, rest };
|
|
68
|
+
}
|
|
69
|
+
/** Check every path before uploading any, so one bad path can't leave the
|
|
70
|
+
* others uploaded and unsent. */
|
|
71
|
+
function checkFiles(paths) {
|
|
72
|
+
const problems = [];
|
|
73
|
+
if (paths.length > exports.MAX_PING_FILES) {
|
|
74
|
+
problems.push({ path: "--file", reason: `at most ${exports.MAX_PING_FILES} files per message` });
|
|
75
|
+
}
|
|
76
|
+
for (const p of paths) {
|
|
77
|
+
let stat;
|
|
78
|
+
try {
|
|
79
|
+
stat = fs_1.default.statSync(p);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
problems.push({ path: p, reason: "file not found" });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!stat.isFile())
|
|
86
|
+
problems.push({ path: p, reason: "not a file" });
|
|
87
|
+
else if (stat.size === 0)
|
|
88
|
+
problems.push({ path: p, reason: "file is empty" });
|
|
89
|
+
else if (stat.size > exports.MAX_PING_FILE_BYTES) {
|
|
90
|
+
problems.push({ path: p, reason: `larger than 20 MB (${formatBytes(stat.size)})` });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return problems;
|
|
94
|
+
}
|
|
95
|
+
function uploadOne(config, filePath) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const name = path_1.default.basename(filePath);
|
|
98
|
+
const data = fs_1.default.readFileSync(filePath);
|
|
99
|
+
const url = new URL("/api/cli/ping-attachment", config.backendUrl);
|
|
100
|
+
url.searchParams.set("name", name);
|
|
101
|
+
const lib = url.protocol === "https:" ? https_1.default : http_1.default;
|
|
102
|
+
const req = lib.request({
|
|
103
|
+
hostname: url.hostname,
|
|
104
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
105
|
+
path: url.pathname + url.search,
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
Authorization: `Bearer ${config.token}`,
|
|
109
|
+
"X-Codiedev-Cli-Version": version_1.CLI_VERSION,
|
|
110
|
+
"Content-Type": contentTypeFor(name),
|
|
111
|
+
"Content-Length": data.length,
|
|
112
|
+
},
|
|
113
|
+
}, (res) => {
|
|
114
|
+
let chunks = "";
|
|
115
|
+
res.on("data", (c) => (chunks += c));
|
|
116
|
+
res.on("end", () => {
|
|
117
|
+
let parsed = {};
|
|
118
|
+
try {
|
|
119
|
+
parsed = chunks ? JSON.parse(chunks) : {};
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
/* fall through to the status check */
|
|
123
|
+
}
|
|
124
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300 && parsed.attachment?.id) {
|
|
125
|
+
resolve(parsed.attachment.id);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
reject(new Error(`${name}: ${parsed.error ?? `HTTP ${res.statusCode}`}`));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
req.on("error", (err) => reject(new Error(`${name}: ${err.message}`)));
|
|
133
|
+
req.write(data);
|
|
134
|
+
req.end();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Upload in order; the returned ids go in the ping's `attachmentIds`. */
|
|
138
|
+
async function uploadPingFiles(config, paths) {
|
|
139
|
+
const ids = [];
|
|
140
|
+
for (const p of paths)
|
|
141
|
+
ids.push(await uploadOne(config, p));
|
|
142
|
+
return ids;
|
|
143
|
+
}
|
|
144
|
+
/** Validate, then upload. Prints the problems and exits on a bad path. */
|
|
145
|
+
async function uploadOrExit(config, paths) {
|
|
146
|
+
if (paths.length === 0)
|
|
147
|
+
return [];
|
|
148
|
+
const problems = checkFiles(paths);
|
|
149
|
+
if (problems.length) {
|
|
150
|
+
for (const p of problems)
|
|
151
|
+
console.error(`Can't attach ${p.path}: ${p.reason}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
return await uploadPingFiles(config, paths);
|
|
155
|
+
}
|