codiedev 0.10.0-beta.16 → 0.10.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +63 -18
- package/dist/codie.js +19 -2
- package/dist/codieLauncher.js +20 -1
- package/dist/commands/agents.js +18 -1
- package/dist/commands/agentsSdk.generated.js +3 -3
- package/dist/commands/inbox.js +9 -0
- package/dist/commands/pack.js +258 -12
- package/dist/commands/ping.js +35 -10
- package/dist/commands/pingFiles.js +145 -0
- package/dist/commands/push.js +33 -4
- package/dist/commands/reply.js +49 -0
- package/dist/commands/scrub.js +1 -2
- package/dist/commands/send.js +11 -51
- package/dist/commands/shared.js +51 -0
- package/dist/commands/work.js +761 -0
- package/dist/connect.js +3 -0
- package/dist/hook.js +50 -5
- package/dist/inboxFormat.js +17 -1
- package/dist/launchContext.js +56 -0
- package/dist/launcher.js +24 -1
- package/dist/managedPi.js +1 -0
- package/dist/mcp.js +15 -77
- package/dist/packs/files.js +291 -0
- package/dist/packs/harnesses.js +2 -3
- package/dist/packs/install.js +31 -3
- package/dist/packs/localSkills.js +120 -0
- package/dist/packs/manifest.js +51 -2
- package/dist/packs/skillFiles.js +106 -0
- package/dist/packs/store.js +21 -3
- package/dist/promptGuard.js +223 -0
- package/dist/scrub/rules.js +260 -21
- package/dist/teamSkillContext.js +1 -1
- package/dist/unreadNotice.js +72 -0
- package/dist/utils.js +120 -25
- package/package.json +2 -2
- package/dist/commands/ask.js +0 -55
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;
|
|
@@ -45,6 +46,9 @@ exports.commitExport = commitExport;
|
|
|
45
46
|
exports.exportPluginFolder = exportPluginFolder;
|
|
46
47
|
exports.runExport = runExport;
|
|
47
48
|
exports.runApply = runApply;
|
|
49
|
+
exports.runPublish = runPublish;
|
|
50
|
+
exports.skillKeyFor = skillKeyFor;
|
|
51
|
+
exports.pushLocalSkill = pushLocalSkill;
|
|
48
52
|
const install_1 = require("../packs/install");
|
|
49
53
|
const harnesses_1 = require("../packs/harnesses");
|
|
50
54
|
const manifest_1 = require("../packs/manifest");
|
|
@@ -58,7 +62,12 @@ const sync_1 = require("../packs/sync");
|
|
|
58
62
|
const harnesses_2 = require("../packs/harnesses");
|
|
59
63
|
const os = __importStar(require("os"));
|
|
60
64
|
const utils_1 = require("../utils");
|
|
65
|
+
const push_1 = require("./push");
|
|
66
|
+
const skillFiles_1 = require("../packs/skillFiles");
|
|
67
|
+
const skillMarker_1 = require("../skillMarker");
|
|
68
|
+
const localSkills_1 = require("../packs/localSkills");
|
|
61
69
|
const shared_1 = require("./shared");
|
|
70
|
+
const files_1 = require("../packs/files");
|
|
62
71
|
exports.DEFAULT_BACKEND_URL = "https://cli.codiedev.com";
|
|
63
72
|
function resolvePackConfig(env = process.env) {
|
|
64
73
|
const token = env.CODIEDEV_TOKEN?.trim();
|
|
@@ -96,7 +105,27 @@ const USAGE = `Usage:
|
|
|
96
105
|
.claude-plugin/marketplace.json (commit it for CI).
|
|
97
106
|
--git inits the folder and commits; --manifest writes
|
|
98
107
|
an editable <slug>.pack.md instead
|
|
99
|
-
codiedev pack apply <pack.md>
|
|
108
|
+
codiedev pack apply <pack.md> [--publish] [--channel stable|beta|alpha] [--dry-run]
|
|
109
|
+
Create or update a pack from a pack.md
|
|
110
|
+
(--publish then publishes it, see below)
|
|
111
|
+
codiedev pack publish <slug> [--channel stable|beta|alpha]
|
|
112
|
+
Publish the pack's draft to a channel (default
|
|
113
|
+
stable). Nothing happens when it hasn't changed.
|
|
114
|
+
|
|
115
|
+
Files (scripts, PDFs, a runner folder): list them under "## Files" in pack.md,
|
|
116
|
+
one "- <path in pack> <- <local path>" per line (local paths are relative to
|
|
117
|
+
the pack.md; "." is the pack root):
|
|
118
|
+
|
|
119
|
+
## Files
|
|
120
|
+
|
|
121
|
+
- runner <- ~/.claude/runner
|
|
122
|
+
- skills/healthcare-scan <- ~/.claude/skills/healthcare-scan
|
|
123
|
+
|
|
124
|
+
\`pack apply\` uploads them (admins and team managers; 15MB per file, 50MB per
|
|
125
|
+
pack; caches, .git and hidden files are skipped). Without a "## Files"
|
|
126
|
+
section the pack's files are left alone. In skills, point at files relative to
|
|
127
|
+
the skill's folder, or use \${CLAUDE_PLUGIN_ROOT}/runner/...: Codiedev swaps in the
|
|
128
|
+
real path for Codex, Copilot, Cursor, pi and Codie.
|
|
100
129
|
|
|
101
130
|
Cursor: packs are copied to ~/.cursor/plugins/local/codiedev-<slug>/. A Cursor
|
|
102
131
|
team admin can disable "user local plugin imports"; then Cursor ignores them.
|
|
@@ -133,8 +162,25 @@ got the pack. Skills from a pack are /<pack-slug>:<skill> in Claude Code.
|
|
|
133
162
|
env:
|
|
134
163
|
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
135
164
|
|
|
165
|
+
Keep a pack up to date from its repo (GitLab; on merges to main). Use a
|
|
166
|
+
personal Codiedev token of a pack owner (portal → Integrations → CLI tokens) as a
|
|
167
|
+
masked CODIEDEV_TOKEN: pack CI keys can only install, not publish.
|
|
168
|
+
pack-release:
|
|
169
|
+
image: node:22
|
|
170
|
+
rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }]
|
|
171
|
+
script:
|
|
172
|
+
- npm i -g codiedev
|
|
173
|
+
- codiedev pack apply packs/platform-starter.pack.md --publish
|
|
174
|
+
Skills listed in pack.md as local folders ("- ./skills/healthcare-scan") are
|
|
175
|
+
pushed when their SKILL.md changed, and the rest of each folder ships with it.
|
|
176
|
+
|
|
136
177
|
Or vendor the plugin into the repo instead of installing at build time:
|
|
137
178
|
codiedev pack export <slug> --out .codiedev/packs`;
|
|
179
|
+
function parseChannel(value) {
|
|
180
|
+
if (value === "stable" || value === "beta" || value === "alpha")
|
|
181
|
+
return value;
|
|
182
|
+
throw new Error(`--channel must be stable, beta or alpha (got ${value})`);
|
|
183
|
+
}
|
|
138
184
|
function parsePackArgs(args) {
|
|
139
185
|
const out = { dryRun: false, json: false };
|
|
140
186
|
const positional = [];
|
|
@@ -152,6 +198,15 @@ function parsePackArgs(args) {
|
|
|
152
198
|
out.git = true;
|
|
153
199
|
else if (a === "--manifest")
|
|
154
200
|
out.manifest = true;
|
|
201
|
+
else if (a === "--publish")
|
|
202
|
+
out.publish = true;
|
|
203
|
+
else if (a === "--channel") {
|
|
204
|
+
if (i + 1 >= args.length)
|
|
205
|
+
throw new Error("--channel needs stable, beta or alpha");
|
|
206
|
+
out.channel = parseChannel(args[++i]);
|
|
207
|
+
}
|
|
208
|
+
else if (a.startsWith("--channel="))
|
|
209
|
+
out.channel = parseChannel(a.slice("--channel=".length));
|
|
155
210
|
else if (a === "--out" || a === "-o") {
|
|
156
211
|
if (i + 1 >= args.length)
|
|
157
212
|
throw new Error("--out needs a directory");
|
|
@@ -183,6 +238,15 @@ async function fetchManifest(config, slug) {
|
|
|
183
238
|
const raw = await (0, shared_1.apiRequest)("GET", "/api/cli/packs/manifest", { config, query: { slug } });
|
|
184
239
|
return (0, manifest_1.parseManifest)(raw);
|
|
185
240
|
}
|
|
241
|
+
async function downloadPackFiles(config, slug, files, dir) {
|
|
242
|
+
if (!files.length)
|
|
243
|
+
return 0;
|
|
244
|
+
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 })
|
|
245
|
+
.catch((error) => {
|
|
246
|
+
throw new Error(`couldn't download ${file.path} (${error.message})`);
|
|
247
|
+
}));
|
|
248
|
+
return downloaded;
|
|
249
|
+
}
|
|
186
250
|
async function runList(config, json, overrides = {}) {
|
|
187
251
|
const packs = await listPacks(config);
|
|
188
252
|
const registry = (0, store_1.readRegistry)((0, paths_1.packsRoot)(overrides.env, overrides.home));
|
|
@@ -201,6 +265,7 @@ async function runList(config, json, overrides = {}) {
|
|
|
201
265
|
`${p.itemCount ?? 0} skill${p.itemCount === 1 ? "" : "s"}`,
|
|
202
266
|
`${p.agentCount ?? 0} agent${p.agentCount === 1 ? "" : "s"}`,
|
|
203
267
|
...(p.hasHooks ? ["hooks"] : []),
|
|
268
|
+
...(p.fileCount ? [`${p.fileCount} file${p.fileCount === 1 ? "" : "s"}`] : []),
|
|
204
269
|
].join(", ");
|
|
205
270
|
const channel = (0, manifest_2.cleanLabel)(p.channel, 16);
|
|
206
271
|
console.log(`${p.slug} v${p.version}${channel ? ` [${channel}]` : ""} ${(0, manifest_2.displayName)(p)} (${counts}) — ${state}`);
|
|
@@ -236,8 +301,25 @@ async function runInstall(config, args, overrides = {}) {
|
|
|
236
301
|
throw new Error(`Pack not found or not assigned to your team: ${slug}`);
|
|
237
302
|
}
|
|
238
303
|
}
|
|
239
|
-
|
|
240
|
-
|
|
304
|
+
const blobDir = overrides.blobDir ?? (0, files_1.blobsDir)(root);
|
|
305
|
+
const ready = [];
|
|
306
|
+
let downloadFailed = false;
|
|
307
|
+
for (const manifest of manifests) {
|
|
308
|
+
if (args.dryRun || !manifest.files?.length) {
|
|
309
|
+
ready.push(manifest);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
await downloadPackFiles(config, manifest.slug, manifest.files, blobDir);
|
|
314
|
+
ready.push(manifest);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
downloadFailed = true;
|
|
318
|
+
console.error(`✗ ${manifest.slug}: ${error.message}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
let installed = ready.length
|
|
322
|
+
? (0, install_1.installPacks)({ ...overrides, manifests: ready, harnesses, dryRun: args.dryRun, blobDir })
|
|
241
323
|
: [];
|
|
242
324
|
let removed = [];
|
|
243
325
|
if (!args.dryRun && !ci) {
|
|
@@ -279,7 +361,7 @@ async function runInstall(config, args, overrides = {}) {
|
|
|
279
361
|
const ciFailed = ci && !args.dryRun && (installed.length === 0 || installed.some((p) => p.results.some((r) => r.status !== "ok")));
|
|
280
362
|
if (ci && !args.dryRun && installed.length === 0)
|
|
281
363
|
console.error("No pack is visible to this key.");
|
|
282
|
-
if (failed || ciFailed || (args.slug && removed.length + revoked.length > 0))
|
|
364
|
+
if (failed || ciFailed || downloadFailed || (args.slug && removed.length + revoked.length > 0))
|
|
283
365
|
process.exitCode = 1;
|
|
284
366
|
return run;
|
|
285
367
|
}
|
|
@@ -390,14 +472,14 @@ async function runPack(args) {
|
|
|
390
472
|
console.error(USAGE);
|
|
391
473
|
process.exit(1);
|
|
392
474
|
}
|
|
393
|
-
if (!parsed.sub || !["list", "ls", "install", "status", "export", "apply"].includes(parsed.sub)) {
|
|
475
|
+
if (!parsed.sub || !["list", "ls", "install", "status", "export", "apply", "publish"].includes(parsed.sub)) {
|
|
394
476
|
console.error(USAGE);
|
|
395
477
|
process.exit(parsed.sub ? 1 : 0);
|
|
396
478
|
}
|
|
397
479
|
const config = resolvePackConfig() ?? (0, shared_1.requireConfig)();
|
|
398
480
|
const ci = (0, sync_1.isPackCiMode)();
|
|
399
481
|
const restore = parsed.quiet ? silenceConsole() : () => { };
|
|
400
|
-
const track = ci ? (_c, _t, fn) => fn() : shared_1.withTelemetry;
|
|
482
|
+
const track = ci || parsed.quiet ? (_c, _t, fn) => fn() : shared_1.withTelemetry;
|
|
401
483
|
try {
|
|
402
484
|
await track(config, `codiedev_pack_${parsed.sub === "ls" ? "list" : parsed.sub}`, async () => {
|
|
403
485
|
if (parsed.sub === "list" || parsed.sub === "ls")
|
|
@@ -408,6 +490,8 @@ async function runPack(args) {
|
|
|
408
490
|
await runExport(config, parsed);
|
|
409
491
|
else if (parsed.sub === "apply")
|
|
410
492
|
await runApply(config, parsed);
|
|
493
|
+
else if (parsed.sub === "publish")
|
|
494
|
+
await runPublish(config, parsed);
|
|
411
495
|
else
|
|
412
496
|
await runStatus(config, parsed.json, {}, { prune: !parsed.noPrune });
|
|
413
497
|
});
|
|
@@ -468,9 +552,9 @@ function readMarketplaceEntries(dir) {
|
|
|
468
552
|
return [];
|
|
469
553
|
}
|
|
470
554
|
}
|
|
471
|
-
function exportPluginFolder(out, manifest) {
|
|
555
|
+
function exportPluginFolder(out, manifest, blobDir) {
|
|
472
556
|
fs.mkdirSync(out, { recursive: true });
|
|
473
|
-
const dir = (0, store_1.writePluginDir)(out, manifest);
|
|
557
|
+
const dir = (0, store_1.writePluginDir)(out, manifest, { blobDir });
|
|
474
558
|
const entries = readMarketplaceEntries(out).filter((e) => e.slug !== manifest.slug);
|
|
475
559
|
entries.push({ slug: manifest.slug, description: manifest.description || manifest.name, version: manifest.version });
|
|
476
560
|
(0, store_1.writeMarketplace)(out, entries);
|
|
@@ -497,7 +581,22 @@ async function runExport(config, args) {
|
|
|
497
581
|
throw new Error("Server returned no markdown");
|
|
498
582
|
fs.mkdirSync(out, { recursive: true });
|
|
499
583
|
written = path.join(out, `${args.slug}.pack.md`);
|
|
500
|
-
|
|
584
|
+
let markdown = res.markdown.endsWith("\n") ? res.markdown : `${res.markdown}\n`;
|
|
585
|
+
const files = (0, manifest_1.parseManifest)({ slug: args.slug, version: 0, contentHash: "-", files: res.files }).files ?? [];
|
|
586
|
+
if (files.length) {
|
|
587
|
+
const blobDir = (0, files_1.blobsDir)((0, paths_1.packsRoot)());
|
|
588
|
+
await downloadPackFiles(config, args.slug, files, blobDir);
|
|
589
|
+
const filesDir = path.join(out, `${args.slug}.files`);
|
|
590
|
+
fs.rmSync(filesDir, { recursive: true, force: true });
|
|
591
|
+
for (const f of files) {
|
|
592
|
+
const target = path.join(filesDir, ...f.path.split("/"));
|
|
593
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
594
|
+
fs.copyFileSync((0, files_1.blobPath)(blobDir, f.sha256), target);
|
|
595
|
+
fs.chmodSync(target, f.executable ? 0o755 : 0o644);
|
|
596
|
+
}
|
|
597
|
+
markdown = `${markdown.trimEnd()}\n\n## Files\n\n- . <- ./${args.slug}.files\n`;
|
|
598
|
+
}
|
|
599
|
+
(0, store_1.writeFileAtomic)(written, markdown);
|
|
501
600
|
message = `codiedev pack export ${args.slug} (${args.slug}.pack.md)`;
|
|
502
601
|
}
|
|
503
602
|
else {
|
|
@@ -512,7 +611,9 @@ async function runExport(config, args) {
|
|
|
512
611
|
throw new Error(`Refusing to export ${args.slug}: ${error.message}`);
|
|
513
612
|
throw error;
|
|
514
613
|
}
|
|
515
|
-
|
|
614
|
+
const blobDir = (0, files_1.blobsDir)((0, paths_1.packsRoot)());
|
|
615
|
+
await downloadPackFiles(config, manifest.slug, manifest.files ?? [], blobDir);
|
|
616
|
+
written = exportPluginFolder(out, manifest, blobDir);
|
|
516
617
|
message = `codiedev pack export ${manifest.slug} v${manifest.version}`;
|
|
517
618
|
}
|
|
518
619
|
const commit = args.git ? commitExport(out, message) : undefined;
|
|
@@ -540,13 +641,53 @@ async function runApply(config, args) {
|
|
|
540
641
|
}
|
|
541
642
|
if (!markdown.trim())
|
|
542
643
|
throw new Error(`${file} is empty`);
|
|
644
|
+
const baseDir = path.dirname(file);
|
|
645
|
+
const skills = (0, localSkills_1.findLocalSkills)(markdown, baseDir);
|
|
646
|
+
const entries = (0, files_1.parseFilesSection)(markdown);
|
|
647
|
+
const local = entries ? (0, files_1.collectLocalFiles)(entries, baseDir) : null;
|
|
648
|
+
for (const note of local?.notes ?? [])
|
|
649
|
+
console.log(` · ${note}`);
|
|
543
650
|
if (args.dryRun) {
|
|
544
|
-
|
|
651
|
+
const extra = local ? `, ${local.files.length} file${local.files.length === 1 ? "" : "s"} (${formatBytes(local.files.reduce((n, f) => n + f.size, 0))})` : "";
|
|
652
|
+
console.log(`Dry run — would apply ${file} (${Buffer.byteLength(markdown)} bytes${extra}).`);
|
|
653
|
+
for (const skill of skills) {
|
|
654
|
+
const folder = skill.dir ? (0, skillFiles_1.skillFolderFiles)(skill.skillFile) : null;
|
|
655
|
+
const files = folder && "files" in folder ? folder : null;
|
|
656
|
+
const withFiles = files?.files.length ? `, with ${files.files.length} file${files.files.length === 1 ? "" : "s"} from its folder` : "";
|
|
657
|
+
console.log(` skill ${skill.name} ← ${path.relative(baseDir, skill.skillFile) || skill.skillFile} (pushed if changed${withFiles})`);
|
|
658
|
+
if (folder && "skipped" in folder)
|
|
659
|
+
console.log(` ! SKILL.md only: ${folder.skipped}`);
|
|
660
|
+
for (const note of files?.notes ?? [])
|
|
661
|
+
console.log(` · ${note}`);
|
|
662
|
+
for (const f of files?.files ?? [])
|
|
663
|
+
console.log(` ${skill.pluginDir}/${f.path}`);
|
|
664
|
+
}
|
|
665
|
+
for (const f of local?.files ?? [])
|
|
666
|
+
console.log(` ${f.path}`);
|
|
545
667
|
return {};
|
|
546
668
|
}
|
|
669
|
+
const keys = new Map();
|
|
670
|
+
let pushedSkills = 0;
|
|
671
|
+
for (const skill of skills) {
|
|
672
|
+
const { key, pushed } = await pushLocalSkill(config, skill);
|
|
673
|
+
keys.set(skill.line, key);
|
|
674
|
+
if (pushed)
|
|
675
|
+
pushedSkills += 1;
|
|
676
|
+
}
|
|
677
|
+
if (keys.size)
|
|
678
|
+
markdown = (0, localSkills_1.rewriteLocalSkills)(markdown, keys);
|
|
679
|
+
let uploaded = 0;
|
|
680
|
+
if (local?.files.length)
|
|
681
|
+
uploaded = await uploadPackFiles(config, local.files);
|
|
547
682
|
let res;
|
|
548
683
|
try {
|
|
549
|
-
res = await (0, shared_1.apiRequest)("POST", "/api/cli/packs/apply", {
|
|
684
|
+
res = await (0, shared_1.apiRequest)("POST", "/api/cli/packs/apply", {
|
|
685
|
+
config,
|
|
686
|
+
body: {
|
|
687
|
+
markdown,
|
|
688
|
+
...(local ? { files: local.files.map((f) => ({ path: f.path, sha256: f.sha256, ...(f.executable ? { executable: true } : {}) })) } : {}),
|
|
689
|
+
},
|
|
690
|
+
});
|
|
550
691
|
}
|
|
551
692
|
catch (error) {
|
|
552
693
|
const e = error;
|
|
@@ -561,6 +702,10 @@ async function runApply(config, args) {
|
|
|
561
702
|
const verb = res.created === true ? "Created" : res.changed === false ? "No changes to" : "Updated";
|
|
562
703
|
const version = typeof res.version === "number" ? ` v${res.version}` : "";
|
|
563
704
|
console.log(`✓ ${verb} ${slug}${version} from ${path.basename(file)}`);
|
|
705
|
+
if (skills.length)
|
|
706
|
+
console.log(` skills: ${skills.length} local (${pushedSkills} pushed, ${skills.length - pushedSkills} unchanged)`);
|
|
707
|
+
if (local)
|
|
708
|
+
console.log(` files: ${local.files.length} (${uploaded} uploaded, ${local.files.length - uploaded} already on Codiedev)`);
|
|
564
709
|
if (Array.isArray(res.warnings)) {
|
|
565
710
|
for (const w of res.warnings) {
|
|
566
711
|
const text = typeof w === "string" ? w : typeof w?.message === "string" ? `${w.agent ? `${w.agent}: ` : ""}${w.message}` : JSON.stringify(w);
|
|
@@ -568,6 +713,107 @@ async function runApply(config, args) {
|
|
|
568
713
|
}
|
|
569
714
|
}
|
|
570
715
|
}
|
|
716
|
+
if (args.publish && typeof res.slug === "string") {
|
|
717
|
+
const published = await runPublish(config, { ...args, slug: res.slug, json: false });
|
|
718
|
+
if (args.json)
|
|
719
|
+
return { ...res, published };
|
|
720
|
+
}
|
|
571
721
|
return res;
|
|
572
722
|
}
|
|
723
|
+
async function runPublish(config, args) {
|
|
724
|
+
if (!args.slug)
|
|
725
|
+
throw new Error("Usage: codiedev pack publish <slug> [--channel stable|beta|alpha]");
|
|
726
|
+
if (!(0, manifest_2.isSafeSegment)(args.slug))
|
|
727
|
+
throw new Error(`Not a pack slug: ${args.slug}`);
|
|
728
|
+
const channel = args.channel ?? "stable";
|
|
729
|
+
if (args.dryRun) {
|
|
730
|
+
console.log(`Dry run — would publish ${args.slug}'s draft to ${channel}.`);
|
|
731
|
+
return { slug: args.slug, channel, version: 0, unchanged: true };
|
|
732
|
+
}
|
|
733
|
+
let res;
|
|
734
|
+
try {
|
|
735
|
+
res = await (0, shared_1.apiRequest)("POST", "/api/cli/packs/publish", { config, body: { slug: args.slug, channel } });
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
const e = error;
|
|
739
|
+
if (e.status === 404)
|
|
740
|
+
throw new Error(`Pack not found: ${args.slug}`);
|
|
741
|
+
if (e.status === 403)
|
|
742
|
+
throw new Error(`You can't publish ${args.slug} (only its owners and admins can)`);
|
|
743
|
+
throw new Error(e.message);
|
|
744
|
+
}
|
|
745
|
+
const out = {
|
|
746
|
+
slug: typeof res.slug === "string" ? res.slug : args.slug,
|
|
747
|
+
channel,
|
|
748
|
+
version: typeof res.version === "number" ? res.version : 0,
|
|
749
|
+
unchanged: res.unchanged === true,
|
|
750
|
+
};
|
|
751
|
+
if (args.json)
|
|
752
|
+
console.log(JSON.stringify(out));
|
|
753
|
+
else if (out.unchanged)
|
|
754
|
+
console.log(`· ${out.slug} v${out.version} is already on ${channel} (no changes to publish)`);
|
|
755
|
+
else
|
|
756
|
+
console.log(`✓ Published ${out.slug} v${out.version} to ${channel}. Teams on ${channel} get it at their next sync.`);
|
|
757
|
+
return out;
|
|
758
|
+
}
|
|
759
|
+
function skillKeyFor(name) {
|
|
760
|
+
const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64).replace(/-+$/g, "");
|
|
761
|
+
return slug ? `${slug}.md` : name;
|
|
762
|
+
}
|
|
763
|
+
async function pushLocalSkill(config, skill) {
|
|
764
|
+
const local = (0, skillMarker_1.stripSkillMarker)(fs.readFileSync(skill.skillFile, "utf8"));
|
|
765
|
+
const found = skill.dir ? (0, skillFiles_1.skillFolderFiles)(skill.skillFile) : null;
|
|
766
|
+
if (found && "skipped" in found)
|
|
767
|
+
console.log(` ! ${skill.name}: pushing SKILL.md only, ${found.skipped}. The skill keeps the files it had.`);
|
|
768
|
+
const folder = found && "files" in found ? found : null;
|
|
769
|
+
for (const note of folder?.notes ?? [])
|
|
770
|
+
console.log(` · ${skill.name}: ${note}`);
|
|
771
|
+
const localRefs = folder ? folder.files.map(skillFiles_1.toRef) : undefined;
|
|
772
|
+
for (const key of [skillKeyFor(skill.name), skill.name]) {
|
|
773
|
+
try {
|
|
774
|
+
const current = await (0, shared_1.apiRequest)("GET", "/api/cli/pull", { config, query: { key } });
|
|
775
|
+
const remote = current.artifact?.markdown;
|
|
776
|
+
if (current.artifact?.kind === "skill" &&
|
|
777
|
+
typeof remote === "string" &&
|
|
778
|
+
(0, skillMarker_1.stripSkillMarker)(remote).trimEnd() === local.trimEnd() &&
|
|
779
|
+
(!localRefs || (0, skillFiles_1.sameFileRefs)(current.artifact.files, localRefs))) {
|
|
780
|
+
return { key: current.artifact.key ?? key, pushed: false };
|
|
781
|
+
}
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
catch (error) {
|
|
785
|
+
if (error.status !== 404)
|
|
786
|
+
throw new Error(`couldn't check skill ${skill.name} (${error.message})`);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
if (folder)
|
|
791
|
+
await (0, skillFiles_1.uploadBlobs)(config, folder.files, "/api/cli/skills/files");
|
|
792
|
+
const res = await (0, push_1.pushFile)(config, {
|
|
793
|
+
absPath: skill.skillFile,
|
|
794
|
+
key: path.basename(skill.skillFile),
|
|
795
|
+
kind: "skill",
|
|
796
|
+
visibility: "team",
|
|
797
|
+
...(localRefs ? { files: localRefs } : {}),
|
|
798
|
+
});
|
|
799
|
+
return { key: res.key ?? skill.name, pushed: true };
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
throw new Error(`couldn't push skill ${skill.name} (${error.message})`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function formatBytes(n) {
|
|
806
|
+
return n >= 1024 * 1024 ? `${(n / (1024 * 1024)).toFixed(1)}MB` : n >= 1024 ? `${Math.round(n / 1024)}KB` : `${n}B`;
|
|
807
|
+
}
|
|
808
|
+
async function uploadPackFiles(config, files) {
|
|
809
|
+
try {
|
|
810
|
+
return await (0, skillFiles_1.uploadBlobs)(config, files, "/api/cli/packs/files");
|
|
811
|
+
}
|
|
812
|
+
catch (error) {
|
|
813
|
+
const e = error;
|
|
814
|
+
if (e.status === 403)
|
|
815
|
+
throw new Error(`Only admins and team managers can add files to a pack (${e.message})`);
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
573
819
|
exports.PACK_HELP = USAGE;
|
package/dist/commands/ping.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
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;
|
|
4
6
|
const shared_1 = require("./shared");
|
|
5
|
-
|
|
7
|
+
const pingFiles_1 = require("./pingFiles");
|
|
8
|
+
const share_1 = require("./share");
|
|
9
|
+
exports.PING_USAGE = 'Usage: codiedev ping <user> ["<message>"] [--with <artifact-key>] [--edit] [--file <path>]';
|
|
10
|
+
function parsePingArgs(allArgs) {
|
|
11
|
+
const { files, rest: args } = (0, pingFiles_1.takeFileArgs)(allArgs);
|
|
6
12
|
let to;
|
|
7
13
|
const positional = [];
|
|
8
14
|
let subjectKey;
|
|
15
|
+
let edit = false;
|
|
9
16
|
for (let i = 0; i < args.length; i++) {
|
|
10
17
|
const a = args[i];
|
|
11
|
-
if (
|
|
18
|
+
if (a === "--edit") {
|
|
19
|
+
edit = true;
|
|
20
|
+
}
|
|
21
|
+
else if ((a === "--with" || a === "-w") && i + 1 < args.length) {
|
|
12
22
|
subjectKey = args[++i];
|
|
13
23
|
}
|
|
14
24
|
else if (a.startsWith("--with=")) {
|
|
@@ -18,29 +28,44 @@ function parseArgs(args) {
|
|
|
18
28
|
positional.push(a);
|
|
19
29
|
}
|
|
20
30
|
}
|
|
21
|
-
if (positional.length < 2) {
|
|
22
|
-
console.error(
|
|
31
|
+
if (positional.length < 2 && !(positional.length === 1 && (files.length > 0 || subjectKey))) {
|
|
32
|
+
console.error(exports.PING_USAGE);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (edit && !subjectKey) {
|
|
36
|
+
console.error("--edit needs an artifact: --with <artifact-key>");
|
|
23
37
|
process.exit(1);
|
|
24
38
|
}
|
|
25
39
|
to = positional[0];
|
|
26
40
|
const body = (0, shared_1.stripQuotes)(positional.slice(1).join(" "));
|
|
27
|
-
return { to, body, subjectKey };
|
|
41
|
+
return { to, body: body || (files.length ? "Sent a file." : ""), subjectKey, edit, files };
|
|
28
42
|
}
|
|
29
|
-
async function runPing(args) {
|
|
30
|
-
const { to, body, subjectKey } =
|
|
31
|
-
|
|
43
|
+
async function runPing(args, configOverride) {
|
|
44
|
+
const { to, body, subjectKey, edit, files } = parsePingArgs(args);
|
|
45
|
+
if (!body && subjectKey) {
|
|
46
|
+
await (0, share_1.runShare)([subjectKey, to, "--role", edit ? "edit" : "read"], configOverride);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const config = configOverride ?? (0, shared_1.requireConfig)();
|
|
32
50
|
const start = Date.now();
|
|
33
51
|
try {
|
|
52
|
+
const attachmentIds = await (0, pingFiles_1.uploadOrExit)(config, files);
|
|
34
53
|
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ping", {
|
|
35
54
|
config,
|
|
36
|
-
body: {
|
|
55
|
+
body: {
|
|
56
|
+
to,
|
|
57
|
+
body,
|
|
58
|
+
subjectKey,
|
|
59
|
+
...(subjectKey ? { grant: edit ? "edit" : "read" } : {}),
|
|
60
|
+
...(attachmentIds.length ? { attachmentIds } : {}),
|
|
61
|
+
},
|
|
37
62
|
});
|
|
38
63
|
(0, shared_1.recordAgentEvent)(config, {
|
|
39
64
|
tool: "codiedev_ping",
|
|
40
65
|
ok: true,
|
|
41
66
|
latencyMs: Date.now() - start,
|
|
42
67
|
});
|
|
43
|
-
console.log(`✓ Pinged ${res.recipient.name} <${res.recipient.email}>${subjectKey ? ` on ${subjectKey}` : ""}`);
|
|
68
|
+
console.log(`✓ Pinged ${res.recipient.name} <${res.recipient.email}>${subjectKey ? ` on ${subjectKey}` : ""}${files.length ? ` with ${files.length} file${files.length === 1 ? "" : "s"}` : ""}`);
|
|
44
69
|
}
|
|
45
70
|
catch (err) {
|
|
46
71
|
const e = err;
|
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
exports.MAX_PING_FILES = 10;
|
|
19
|
+
exports.MAX_PING_FILE_BYTES = 20 * 1024 * 1024;
|
|
20
|
+
const CONTENT_TYPES = {
|
|
21
|
+
csv: "text/csv",
|
|
22
|
+
tsv: "text/tab-separated-values",
|
|
23
|
+
txt: "text/plain",
|
|
24
|
+
log: "text/plain",
|
|
25
|
+
md: "text/markdown",
|
|
26
|
+
json: "application/json",
|
|
27
|
+
yaml: "text/plain",
|
|
28
|
+
yml: "text/plain",
|
|
29
|
+
diff: "text/plain",
|
|
30
|
+
patch: "text/plain",
|
|
31
|
+
png: "image/png",
|
|
32
|
+
jpg: "image/jpeg",
|
|
33
|
+
jpeg: "image/jpeg",
|
|
34
|
+
gif: "image/gif",
|
|
35
|
+
webp: "image/webp",
|
|
36
|
+
pdf: "application/pdf",
|
|
37
|
+
zip: "application/zip",
|
|
38
|
+
};
|
|
39
|
+
function contentTypeFor(fileName) {
|
|
40
|
+
const ext = path_1.default.extname(fileName).slice(1).toLowerCase();
|
|
41
|
+
return CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
42
|
+
}
|
|
43
|
+
function formatBytes(bytes) {
|
|
44
|
+
if (bytes < 1024)
|
|
45
|
+
return `${bytes} B`;
|
|
46
|
+
if (bytes < 1024 * 1024)
|
|
47
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
48
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
49
|
+
}
|
|
50
|
+
function takeFileArgs(args) {
|
|
51
|
+
const files = [];
|
|
52
|
+
const rest = [];
|
|
53
|
+
for (let i = 0; i < args.length; i++) {
|
|
54
|
+
const a = args[i];
|
|
55
|
+
if ((a === "--file" || a === "-f") && i + 1 < args.length)
|
|
56
|
+
files.push(args[++i]);
|
|
57
|
+
else if (a.startsWith("--file="))
|
|
58
|
+
files.push(a.slice("--file=".length));
|
|
59
|
+
else
|
|
60
|
+
rest.push(a);
|
|
61
|
+
}
|
|
62
|
+
return { files, rest };
|
|
63
|
+
}
|
|
64
|
+
function checkFiles(paths) {
|
|
65
|
+
const problems = [];
|
|
66
|
+
if (paths.length > exports.MAX_PING_FILES) {
|
|
67
|
+
problems.push({ path: "--file", reason: `at most ${exports.MAX_PING_FILES} files per message` });
|
|
68
|
+
}
|
|
69
|
+
for (const p of paths) {
|
|
70
|
+
let stat;
|
|
71
|
+
try {
|
|
72
|
+
stat = fs_1.default.statSync(p);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
problems.push({ path: p, reason: "file not found" });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!stat.isFile())
|
|
79
|
+
problems.push({ path: p, reason: "not a file" });
|
|
80
|
+
else if (stat.size === 0)
|
|
81
|
+
problems.push({ path: p, reason: "file is empty" });
|
|
82
|
+
else if (stat.size > exports.MAX_PING_FILE_BYTES) {
|
|
83
|
+
problems.push({ path: p, reason: `larger than 20 MB (${formatBytes(stat.size)})` });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return problems;
|
|
87
|
+
}
|
|
88
|
+
function uploadOne(config, filePath) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
const name = path_1.default.basename(filePath);
|
|
91
|
+
const data = fs_1.default.readFileSync(filePath);
|
|
92
|
+
const url = new URL("/api/cli/ping-attachment", config.backendUrl);
|
|
93
|
+
url.searchParams.set("name", name);
|
|
94
|
+
const lib = url.protocol === "https:" ? https_1.default : http_1.default;
|
|
95
|
+
const req = lib.request({
|
|
96
|
+
hostname: url.hostname,
|
|
97
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
98
|
+
path: url.pathname + url.search,
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: {
|
|
101
|
+
Authorization: `Bearer ${config.token}`,
|
|
102
|
+
"X-Codiedev-Cli-Version": version_1.CLI_VERSION,
|
|
103
|
+
"Content-Type": contentTypeFor(name),
|
|
104
|
+
"Content-Length": data.length,
|
|
105
|
+
},
|
|
106
|
+
}, (res) => {
|
|
107
|
+
let chunks = "";
|
|
108
|
+
res.on("data", (c) => (chunks += c));
|
|
109
|
+
res.on("end", () => {
|
|
110
|
+
let parsed = {};
|
|
111
|
+
try {
|
|
112
|
+
parsed = chunks ? JSON.parse(chunks) : {};
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
}
|
|
116
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300 && parsed.attachment?.id) {
|
|
117
|
+
resolve(parsed.attachment.id);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
reject(new Error(`${name}: ${parsed.error ?? `HTTP ${res.statusCode}`}`));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
req.on("error", (err) => reject(new Error(`${name}: ${err.message}`)));
|
|
125
|
+
req.write(data);
|
|
126
|
+
req.end();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async function uploadPingFiles(config, paths) {
|
|
130
|
+
const ids = [];
|
|
131
|
+
for (const p of paths)
|
|
132
|
+
ids.push(await uploadOne(config, p));
|
|
133
|
+
return ids;
|
|
134
|
+
}
|
|
135
|
+
async function uploadOrExit(config, paths) {
|
|
136
|
+
if (paths.length === 0)
|
|
137
|
+
return [];
|
|
138
|
+
const problems = checkFiles(paths);
|
|
139
|
+
if (problems.length) {
|
|
140
|
+
for (const p of problems)
|
|
141
|
+
console.error(`Can't attach ${p.path}: ${p.reason}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
return await uploadPingFiles(config, paths);
|
|
145
|
+
}
|