codiedev 0.9.87 → 0.9.89
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 -1
- package/dist/cli.js +11 -8
- package/dist/commands/pack.d.ts +10 -0
- package/dist/commands/pack.js +63 -48
- package/dist/commands/ping.d.ts +10 -1
- package/dist/commands/ping.js +39 -10
- package/dist/commands/push.d.ts +14 -0
- package/dist/commands/push.js +37 -4
- package/dist/commands/send.d.ts +1 -0
- package/dist/commands/send.js +15 -55
- package/dist/commands/share.js +2 -2
- package/dist/mcp.js +14 -29
- package/dist/packs/files.d.ts +5 -1
- package/dist/packs/files.js +17 -3
- package/dist/packs/skillFiles.d.ts +37 -0
- package/dist/packs/skillFiles.js +120 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +6 -6
- package/package.json +1 -1
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/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 { LocalSkill } from "../packs/localSkills";
|
|
5
6
|
import { PackBundleFile } from "../packs/files";
|
|
6
7
|
export declare const DEFAULT_BACKEND_URL = "https://cli.codiedev.com";
|
|
7
8
|
/** CODIEDEV_TOKEN alone is enough: no ~/.codiedev/config.json needed (CI).
|
|
@@ -67,4 +68,13 @@ export interface PublishResult {
|
|
|
67
68
|
/** `codiedev pack publish <slug> [--channel stable|beta|alpha]`: snapshot the
|
|
68
69
|
* draft onto a channel. Re-running with no changes publishes nothing. */
|
|
69
70
|
export declare function runPublish(config: CodiedevConfig, args: PackArgs): Promise<PublishResult>;
|
|
71
|
+
/** The library key a skill name is stored under (server preferredSkillKey). */
|
|
72
|
+
export declare function skillKeyFor(name: string): string;
|
|
73
|
+
/** Push a local skill (and its folder's files) unless the library already
|
|
74
|
+
* has this exact text and files, so CI doesn't add a version on every merge.
|
|
75
|
+
* Team-visible: packs ship to teams. */
|
|
76
|
+
export declare function pushLocalSkill(config: CodiedevConfig, skill: LocalSkill): Promise<{
|
|
77
|
+
key: string;
|
|
78
|
+
pushed: boolean;
|
|
79
|
+
}>;
|
|
70
80
|
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> [--publish] [--channel stable|beta|alpha] [--dry-run]\n Create or update a pack from a pack.md\n (--publish then publishes it, see below)\n codiedev pack publish <slug> [--channel stable|beta|alpha]\n Publish the pack's draft to a channel (default\n stable). Nothing happens when it hasn't changed.\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 Keep a pack up to date from its repo (GitLab; on merges to main). Use a\n personal Codiedev token of a pack owner (portal \u2192 Integrations \u2192 CLI tokens) as a\n masked CODIEDEV_TOKEN: pack CI keys can only install, not publish.\n pack-release:\n image: node:22\n rules: [{ if: '$CI_COMMIT_BRANCH == \"main\"' }]\n script:\n - npm i -g codiedev\n - codiedev pack apply packs/platform-starter.pack.md --publish\n Skills listed in pack.md as local folders (\"- ./skills/healthcare-scan\") are\n pushed when their SKILL.md changed, and the rest of each folder ships with it.\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
|
@@ -47,6 +47,8 @@ exports.exportPluginFolder = exportPluginFolder;
|
|
|
47
47
|
exports.runExport = runExport;
|
|
48
48
|
exports.runApply = runApply;
|
|
49
49
|
exports.runPublish = runPublish;
|
|
50
|
+
exports.skillKeyFor = skillKeyFor;
|
|
51
|
+
exports.pushLocalSkill = pushLocalSkill;
|
|
50
52
|
const install_1 = require("../packs/install");
|
|
51
53
|
const harnesses_1 = require("../packs/harnesses");
|
|
52
54
|
const manifest_1 = require("../packs/manifest");
|
|
@@ -61,6 +63,7 @@ const harnesses_2 = require("../packs/harnesses");
|
|
|
61
63
|
const os = __importStar(require("os"));
|
|
62
64
|
const utils_1 = require("../utils");
|
|
63
65
|
const push_1 = require("./push");
|
|
66
|
+
const skillFiles_1 = require("../packs/skillFiles");
|
|
64
67
|
const skillMarker_1 = require("../skillMarker");
|
|
65
68
|
const localSkills_1 = require("../packs/localSkills");
|
|
66
69
|
const shared_1 = require("./shared");
|
|
@@ -671,29 +674,31 @@ async function runApply(config, args) {
|
|
|
671
674
|
if (!markdown.trim())
|
|
672
675
|
throw new Error(`${file} is empty`);
|
|
673
676
|
const baseDir = path.dirname(file);
|
|
674
|
-
// SIG-128: local skills under `## Skills` are pushed
|
|
675
|
-
//
|
|
677
|
+
// SIG-128: local skills under `## Skills` are pushed; a skill folder's other
|
|
678
|
+
// contents (scripts/, docs/) travel with the skill itself, so every pack
|
|
679
|
+
// that includes it ships them under skills/<name>/.
|
|
676
680
|
const skills = (0, localSkills_1.findLocalSkills)(markdown, baseDir);
|
|
677
|
-
// `## Files` (SIG-127):
|
|
678
|
-
//
|
|
679
|
-
// left as they are; otherwise this list replaces them.
|
|
681
|
+
// `## Files` (SIG-127): shared folders/files for the plugin dir (a runner).
|
|
682
|
+
// No Files section ⇒ the pack's own files are left as they are.
|
|
680
683
|
const entries = (0, files_1.parseFilesSection)(markdown);
|
|
681
|
-
const
|
|
682
|
-
const
|
|
683
|
-
const allEntries = entries || folderEntries.length ? [...(entries ?? []), ...folderEntries] : null;
|
|
684
|
-
const local = allEntries ? (0, files_1.collectLocalFiles)(allEntries, baseDir) : null;
|
|
685
|
-
const ownSkillMd = new Set(skills.map((s) => `${s.pluginDir}/SKILL.md`.toLowerCase()));
|
|
686
|
-
for (const note of local?.notes ?? []) {
|
|
687
|
-
// A local skill's own SKILL.md is pushed as the skill, not skipped.
|
|
688
|
-
if ([...ownSkillMd].some((p) => note.toLowerCase().includes(p)))
|
|
689
|
-
continue;
|
|
684
|
+
const local = entries ? (0, files_1.collectLocalFiles)(entries, baseDir) : null;
|
|
685
|
+
for (const note of local?.notes ?? [])
|
|
690
686
|
console.log(` · ${note}`);
|
|
691
|
-
}
|
|
692
687
|
if (args.dryRun) {
|
|
693
688
|
const extra = local ? `, ${local.files.length} file${local.files.length === 1 ? "" : "s"} (${formatBytes(local.files.reduce((n, f) => n + f.size, 0))})` : "";
|
|
694
689
|
console.log(`Dry run — would apply ${file} (${Buffer.byteLength(markdown)} bytes${extra}).`);
|
|
695
|
-
for (const skill of skills)
|
|
696
|
-
|
|
690
|
+
for (const skill of skills) {
|
|
691
|
+
const folder = skill.dir ? (0, skillFiles_1.skillFolderFiles)(skill.skillFile) : null;
|
|
692
|
+
const files = folder && "files" in folder ? folder : null;
|
|
693
|
+
const withFiles = files?.files.length ? `, with ${files.files.length} file${files.files.length === 1 ? "" : "s"} from its folder` : "";
|
|
694
|
+
console.log(` skill ${skill.name} ← ${path.relative(baseDir, skill.skillFile) || skill.skillFile} (pushed if changed${withFiles})`);
|
|
695
|
+
if (folder && "skipped" in folder)
|
|
696
|
+
console.log(` ! SKILL.md only: ${folder.skipped}`);
|
|
697
|
+
for (const note of files?.notes ?? [])
|
|
698
|
+
console.log(` · ${note}`);
|
|
699
|
+
for (const f of files?.files ?? [])
|
|
700
|
+
console.log(` ${skill.pluginDir}/${f.path}`);
|
|
701
|
+
}
|
|
697
702
|
for (const f of local?.files ?? [])
|
|
698
703
|
console.log(` ${f.path}`);
|
|
699
704
|
return {};
|
|
@@ -792,23 +797,50 @@ async function runPublish(config, args) {
|
|
|
792
797
|
console.log(`✓ Published ${out.slug} v${out.version} to ${channel}. Teams on ${channel} get it at their next sync.`);
|
|
793
798
|
return out;
|
|
794
799
|
}
|
|
795
|
-
/**
|
|
796
|
-
|
|
800
|
+
/** The library key a skill name is stored under (server preferredSkillKey). */
|
|
801
|
+
function skillKeyFor(name) {
|
|
802
|
+
const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64).replace(/-+$/g, "");
|
|
803
|
+
return slug ? `${slug}.md` : name;
|
|
804
|
+
}
|
|
805
|
+
/** Push a local skill (and its folder's files) unless the library already
|
|
806
|
+
* has this exact text and files, so CI doesn't add a version on every merge.
|
|
807
|
+
* Team-visible: packs ship to teams. */
|
|
797
808
|
async function pushLocalSkill(config, skill) {
|
|
798
809
|
const local = (0, skillMarker_1.stripSkillMarker)(fs.readFileSync(skill.skillFile, "utf8"));
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
810
|
+
const found = skill.dir ? (0, skillFiles_1.skillFolderFiles)(skill.skillFile) : null;
|
|
811
|
+
if (found && "skipped" in found)
|
|
812
|
+
console.log(` ! ${skill.name}: pushing SKILL.md only, ${found.skipped}. The skill keeps the files it had.`);
|
|
813
|
+
const folder = found && "files" in found ? found : null;
|
|
814
|
+
for (const note of folder?.notes ?? [])
|
|
815
|
+
console.log(` · ${skill.name}: ${note}`);
|
|
816
|
+
const localRefs = folder ? folder.files.map(skillFiles_1.toRef) : undefined;
|
|
817
|
+
for (const key of [skillKeyFor(skill.name), skill.name]) {
|
|
818
|
+
try {
|
|
819
|
+
const current = await (0, shared_1.apiRequest)("GET", "/api/cli/pull", { config, query: { key } });
|
|
820
|
+
const remote = current.artifact?.markdown;
|
|
821
|
+
if (current.artifact?.kind === "skill" &&
|
|
822
|
+
typeof remote === "string" &&
|
|
823
|
+
(0, skillMarker_1.stripSkillMarker)(remote).trimEnd() === local.trimEnd() &&
|
|
824
|
+
(!localRefs || (0, skillFiles_1.sameFileRefs)(current.artifact.files, localRefs))) {
|
|
825
|
+
return { key: current.artifact.key ?? key, pushed: false };
|
|
826
|
+
}
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
if (error.status !== 404)
|
|
831
|
+
throw new Error(`couldn't check skill ${skill.name} (${error.message})`);
|
|
804
832
|
}
|
|
805
|
-
}
|
|
806
|
-
catch (error) {
|
|
807
|
-
if (error.status !== 404)
|
|
808
|
-
throw new Error(`couldn't check skill ${skill.name} (${error.message})`);
|
|
809
833
|
}
|
|
810
834
|
try {
|
|
811
|
-
|
|
835
|
+
if (folder)
|
|
836
|
+
await (0, skillFiles_1.uploadBlobs)(config, folder.files, "/api/cli/skills/files");
|
|
837
|
+
const res = await (0, push_1.pushFile)(config, {
|
|
838
|
+
absPath: skill.skillFile,
|
|
839
|
+
key: path.basename(skill.skillFile),
|
|
840
|
+
kind: "skill",
|
|
841
|
+
visibility: "team",
|
|
842
|
+
...(localRefs ? { files: localRefs } : {}),
|
|
843
|
+
});
|
|
812
844
|
return { key: res.key ?? skill.name, pushed: true };
|
|
813
845
|
}
|
|
814
846
|
catch (error) {
|
|
@@ -818,12 +850,10 @@ async function pushLocalSkill(config, skill) {
|
|
|
818
850
|
function formatBytes(n) {
|
|
819
851
|
return n >= 1024 * 1024 ? `${(n / (1024 * 1024)).toFixed(1)}MB` : n >= 1024 ? `${Math.round(n / 1024)}KB` : `${n}B`;
|
|
820
852
|
}
|
|
821
|
-
/** Upload the blobs Codiedev doesn't have yet. Returns how many were sent. */
|
|
853
|
+
/** Upload the pack's own blobs Codiedev doesn't have yet. Returns how many were sent. */
|
|
822
854
|
async function uploadPackFiles(config, files) {
|
|
823
|
-
const unique = [...new Map(files.map((f) => [f.sha256, f])).values()];
|
|
824
|
-
let known;
|
|
825
855
|
try {
|
|
826
|
-
|
|
856
|
+
return await (0, skillFiles_1.uploadBlobs)(config, files, "/api/cli/packs/files");
|
|
827
857
|
}
|
|
828
858
|
catch (error) {
|
|
829
859
|
const e = error;
|
|
@@ -831,20 +861,5 @@ async function uploadPackFiles(config, files) {
|
|
|
831
861
|
throw new Error(`Only admins and team managers can add files to a pack (${e.message})`);
|
|
832
862
|
throw error;
|
|
833
863
|
}
|
|
834
|
-
const have = new Set(known);
|
|
835
|
-
let sent = 0;
|
|
836
|
-
for (const f of unique) {
|
|
837
|
-
if (have.has(f.sha256))
|
|
838
|
-
continue;
|
|
839
|
-
const body = fs.readFileSync(f.abs);
|
|
840
|
-
try {
|
|
841
|
-
await (0, shared_1.apiRequestBytes)("POST", "/api/cli/packs/files/upload", { config, body, query: { sha256: f.sha256 } });
|
|
842
|
-
}
|
|
843
|
-
catch (error) {
|
|
844
|
-
throw new Error(`couldn't upload ${f.path} (${error.message})`);
|
|
845
|
-
}
|
|
846
|
-
sent += 1;
|
|
847
|
-
}
|
|
848
|
-
return sent;
|
|
849
864
|
}
|
|
850
865
|
exports.PACK_HELP = USAGE;
|
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/push.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requireConfig } from "./shared";
|
|
2
|
+
import { FileRef } from "../packs/skillFiles";
|
|
2
3
|
interface PushResponse {
|
|
3
4
|
ok: boolean;
|
|
4
5
|
artifactId: string;
|
|
@@ -17,6 +18,8 @@ export interface ParsedArgs {
|
|
|
17
18
|
force: boolean;
|
|
18
19
|
as?: string;
|
|
19
20
|
private?: boolean;
|
|
21
|
+
/** --no-files: push SKILL.md only; the skill keeps its current files. */
|
|
22
|
+
noFiles?: boolean;
|
|
20
23
|
}
|
|
21
24
|
export declare function parseArgs(args: string[]): ParsedArgs;
|
|
22
25
|
export declare function pushFile(config: ReturnType<typeof requireConfig>, opts: {
|
|
@@ -28,6 +31,17 @@ export declare function pushFile(config: ReturnType<typeof requireConfig>, opts:
|
|
|
28
31
|
kind?: string;
|
|
29
32
|
tags?: string[];
|
|
30
33
|
force?: boolean;
|
|
34
|
+
/** A skill folder's other files, already uploaded (uploadBlobs). Omitted =
|
|
35
|
+
* the new version keeps the previous version's files. */
|
|
36
|
+
files?: FileRef[];
|
|
31
37
|
}): Promise<PushResponse>;
|
|
38
|
+
/** For a SKILL.md in its own folder: upload the folder's other files and
|
|
39
|
+
* return their refs. null = send no list (not a skill folder, or one over the
|
|
40
|
+
* limits, which is warned about): the skill keeps its current files. */
|
|
41
|
+
export declare function uploadSkillFolder(config: ReturnType<typeof requireConfig>, skillFile: string, log?: (line: string) => void): Promise<{
|
|
42
|
+
files: FileRef[];
|
|
43
|
+
bytes: number;
|
|
44
|
+
} | null>;
|
|
45
|
+
export declare function formatBytes(n: number): string;
|
|
32
46
|
export declare function runPush(args: string[]): Promise<void>;
|
|
33
47
|
export {};
|
package/dist/commands/push.js
CHANGED
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.parseArgs = parseArgs;
|
|
37
37
|
exports.pushFile = pushFile;
|
|
38
|
+
exports.uploadSkillFolder = uploadSkillFolder;
|
|
39
|
+
exports.formatBytes = formatBytes;
|
|
38
40
|
exports.runPush = runPush;
|
|
39
41
|
const fs = __importStar(require("fs"));
|
|
40
42
|
const skillMarker_1 = require("../skillMarker");
|
|
@@ -42,6 +44,7 @@ const path = __importStar(require("path"));
|
|
|
42
44
|
const shared = __importStar(require("./shared"));
|
|
43
45
|
const shared_1 = require("./shared");
|
|
44
46
|
const utils_1 = require("../utils");
|
|
47
|
+
const skillFiles_1 = require("../packs/skillFiles");
|
|
45
48
|
const VALID_TYPES = new Set([
|
|
46
49
|
"spec",
|
|
47
50
|
"bugfix",
|
|
@@ -58,6 +61,7 @@ function parseArgs(args) {
|
|
|
58
61
|
let force = false;
|
|
59
62
|
let as;
|
|
60
63
|
let priv = false;
|
|
64
|
+
let noFiles = false;
|
|
61
65
|
const tags = [];
|
|
62
66
|
for (let i = 0; i < args.length; i++) {
|
|
63
67
|
const a = args[i];
|
|
@@ -91,12 +95,15 @@ function parseArgs(args) {
|
|
|
91
95
|
else if (a === "--private") {
|
|
92
96
|
priv = true;
|
|
93
97
|
}
|
|
98
|
+
else if (a === "--no-files") {
|
|
99
|
+
noFiles = true;
|
|
100
|
+
}
|
|
94
101
|
else if (!a.startsWith("--") && !file) {
|
|
95
102
|
file = a;
|
|
96
103
|
}
|
|
97
104
|
}
|
|
98
105
|
if (!file) {
|
|
99
|
-
console.error("Usage: codiedev push <file.md> [--as <key>] [--kind doc|skill] [--tag <name>] [--private] [--force]");
|
|
106
|
+
console.error("Usage: codiedev push <file.md> [--as <key>] [--kind doc|skill] [--tag <name>] [--private] [--force] [--no-files]");
|
|
100
107
|
process.exit(1);
|
|
101
108
|
}
|
|
102
109
|
if (type && !VALID_TYPES.has(type)) {
|
|
@@ -118,7 +125,7 @@ function parseArgs(args) {
|
|
|
118
125
|
process.exit(1);
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
|
-
return { file, type, kind, tags, force, as, private: priv };
|
|
128
|
+
return { file, type, kind, tags, force, as, private: priv, noFiles };
|
|
122
129
|
}
|
|
123
130
|
async function pushFile(config, opts) {
|
|
124
131
|
// Never upload another artifact's identity marker (skillMarker.ts).
|
|
@@ -136,11 +143,31 @@ async function pushFile(config, opts) {
|
|
|
136
143
|
gitRemoteUrl,
|
|
137
144
|
visibility: opts.visibility,
|
|
138
145
|
contentHash: opts.contentHash,
|
|
146
|
+
...(opts.files ? { files: opts.files } : {}),
|
|
139
147
|
},
|
|
140
148
|
});
|
|
141
149
|
}
|
|
150
|
+
/** For a SKILL.md in its own folder: upload the folder's other files and
|
|
151
|
+
* return their refs. null = send no list (not a skill folder, or one over the
|
|
152
|
+
* limits, which is warned about): the skill keeps its current files. */
|
|
153
|
+
async function uploadSkillFolder(config, skillFile, log = () => { }) {
|
|
154
|
+
const folder = (0, skillFiles_1.skillFolderFiles)(skillFile);
|
|
155
|
+
if (!folder)
|
|
156
|
+
return null;
|
|
157
|
+
if ("skipped" in folder) {
|
|
158
|
+
log(` ! Pushing SKILL.md only: ${folder.skipped}. The skill keeps the files it had.`);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
for (const note of folder.notes)
|
|
162
|
+
log(` · ${note}`);
|
|
163
|
+
await (0, skillFiles_1.uploadBlobs)(config, folder.files, "/api/cli/skills/files");
|
|
164
|
+
return { files: folder.files.map(skillFiles_1.toRef), bytes: folder.bytes };
|
|
165
|
+
}
|
|
166
|
+
function formatBytes(n) {
|
|
167
|
+
return n >= 1024 * 1024 ? `${(n / (1024 * 1024)).toFixed(1)}MB` : n >= 1024 ? `${Math.round(n / 1024)}KB` : `${n}B`;
|
|
168
|
+
}
|
|
142
169
|
async function runPush(args) {
|
|
143
|
-
const { file, type, kind, tags, force, as, private: priv } = parseArgs(args);
|
|
170
|
+
const { file, type, kind, tags, force, as, private: priv, noFiles } = parseArgs(args);
|
|
144
171
|
const config = (0, shared_1.requireConfig)();
|
|
145
172
|
let absolute = file;
|
|
146
173
|
if (!path.isAbsolute(absolute)) {
|
|
@@ -162,6 +189,8 @@ async function runPush(args) {
|
|
|
162
189
|
const filename = as ?? path.basename(absolute);
|
|
163
190
|
const start = Date.now();
|
|
164
191
|
try {
|
|
192
|
+
// A skill's folder (scripts/, docs/, PDFs) travels with it.
|
|
193
|
+
const folder = !noFiles && kind !== "doc" ? await uploadSkillFolder(config, absolute, (l) => console.log(l)) : null;
|
|
165
194
|
const res = await pushFile(config, {
|
|
166
195
|
absPath: absolute,
|
|
167
196
|
key: filename,
|
|
@@ -170,6 +199,7 @@ async function runPush(args) {
|
|
|
170
199
|
tags,
|
|
171
200
|
force,
|
|
172
201
|
visibility: priv ? "private" : undefined,
|
|
202
|
+
...(folder ? { files: folder.files } : {}),
|
|
173
203
|
});
|
|
174
204
|
(0, shared_1.recordAgentEvent)(config, {
|
|
175
205
|
tool: "codiedev_push",
|
|
@@ -177,7 +207,10 @@ async function runPush(args) {
|
|
|
177
207
|
latencyMs: Date.now() - start,
|
|
178
208
|
});
|
|
179
209
|
const tagSummary = res.tags && res.tags.length > 0 ? ` tags=[${res.tags.join(", ")}]` : "";
|
|
180
|
-
|
|
210
|
+
const fileSummary = folder?.files.length
|
|
211
|
+
? `, with ${folder.files.length} file${folder.files.length === 1 ? "" : "s"} from its folder, ${formatBytes(folder.bytes)}`
|
|
212
|
+
: "";
|
|
213
|
+
console.log(`✓ Pushed ${filename} (kind=${res.kind}${tagSummary}${fileSummary}).`);
|
|
181
214
|
console.log(` Visible under: ${res.destination}`);
|
|
182
215
|
// Echo the *resolved* key the server stored, not the local filename —
|
|
183
216
|
// skills are keyed by their frontmatter name (so two `SKILL.md` files
|
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/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
|
{
|
package/dist/packs/files.d.ts
CHANGED
|
@@ -36,9 +36,13 @@ export declare function expandHome(p: string, home?: string): string;
|
|
|
36
36
|
/** Walk each `## Files` entry. Junk (caches, VCS, editor files) is skipped
|
|
37
37
|
* quietly; symlinks, hidden files and a skill folder's own SKILL.md are
|
|
38
38
|
* skipped with a note. Throws on a missing source or a bad destination. */
|
|
39
|
-
export declare function collectLocalFiles(entries: FilesEntry[], baseDir: string, home?: string
|
|
39
|
+
export declare function collectLocalFiles(entries: FilesEntry[], baseDir: string, home?: string, opts?: {
|
|
40
|
+
lenient?: boolean;
|
|
41
|
+
checkAs?: (dest: string) => string;
|
|
42
|
+
}): {
|
|
40
43
|
files: LocalFile[];
|
|
41
44
|
notes: string[];
|
|
45
|
+
total: number;
|
|
42
46
|
};
|
|
43
47
|
/** Only Claude Code sets ${CLAUDE_PLUGIN_ROOT}. In the copy other tools read,
|
|
44
48
|
* replace it with the plugin dir's real path (forward slashes, which every
|
package/dist/packs/files.js
CHANGED
|
@@ -218,12 +218,15 @@ function expandHome(p, home = os.homedir()) {
|
|
|
218
218
|
/** Walk each `## Files` entry. Junk (caches, VCS, editor files) is skipped
|
|
219
219
|
* quietly; symlinks, hidden files and a skill folder's own SKILL.md are
|
|
220
220
|
* skipped with a note. Throws on a missing source or a bad destination. */
|
|
221
|
-
function collectLocalFiles(entries, baseDir, home = os.homedir()) {
|
|
221
|
+
function collectLocalFiles(entries, baseDir, home = os.homedir(), opts = {}) {
|
|
222
222
|
const files = new Map();
|
|
223
223
|
const notes = [];
|
|
224
224
|
let total = 0;
|
|
225
225
|
const add = (dest, abs) => {
|
|
226
|
-
|
|
226
|
+
// A skill folder's own SKILL.md is the skill itself (pushed separately).
|
|
227
|
+
if (opts.lenient && dest.toLowerCase() === "skill.md")
|
|
228
|
+
return;
|
|
229
|
+
const problem = packFilePathProblem(opts.checkAs ? opts.checkAs(dest) : dest);
|
|
227
230
|
if (problem) {
|
|
228
231
|
const parts = dest.split("/");
|
|
229
232
|
if (parts.length === 3 && parts[0] === "skills" && parts[2].toLowerCase() === "skill.md") {
|
|
@@ -234,9 +237,18 @@ function collectLocalFiles(entries, baseDir, home = os.homedir()) {
|
|
|
234
237
|
notes.push(`skipped hidden file ${dest}`);
|
|
235
238
|
return;
|
|
236
239
|
}
|
|
240
|
+
if (opts.lenient) {
|
|
241
|
+
// A skill folder: skip what can't ship, never fail the push.
|
|
242
|
+
notes.push(`skipped ${dest} (${problem})`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
237
245
|
throw new Error(`can't ship ${dest}: ${problem}`);
|
|
238
246
|
}
|
|
239
247
|
const stat = fs.statSync(abs);
|
|
248
|
+
if (stat.size > exports.PACK_FILE_MAX_BYTES && opts.lenient) {
|
|
249
|
+
notes.push(`skipped ${dest} (over ${exports.PACK_FILE_MAX_BYTES / (1024 * 1024)}MB)`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
240
252
|
if (stat.size > exports.PACK_FILE_MAX_BYTES)
|
|
241
253
|
throw new Error(`${abs} is over ${exports.PACK_FILE_MAX_BYTES / (1024 * 1024)}MB`);
|
|
242
254
|
const key = dest.toLowerCase();
|
|
@@ -291,11 +303,13 @@ function collectLocalFiles(entries, baseDir, home = os.homedir()) {
|
|
|
291
303
|
throw new Error(`pack.md line ${entry.line}: ${entry.source} is not a file or folder`);
|
|
292
304
|
}
|
|
293
305
|
const list = [...files.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
306
|
+
if (opts.lenient)
|
|
307
|
+
return { files: list, notes, total };
|
|
294
308
|
if (list.length > exports.PACK_MAX_FILES)
|
|
295
309
|
throw new Error(`a pack holds at most ${exports.PACK_MAX_FILES} files (found ${list.length})`);
|
|
296
310
|
if (total > exports.PACK_FILES_MAX_TOTAL_BYTES)
|
|
297
311
|
throw new Error(`pack files total over ${exports.PACK_FILES_MAX_TOTAL_BYTES / (1024 * 1024)}MB`);
|
|
298
|
-
return { files: list, notes };
|
|
312
|
+
return { files: list, notes, total };
|
|
299
313
|
}
|
|
300
314
|
// ─── Portable copies ────────────────────────────────────────────────────────
|
|
301
315
|
/** Only Claude Code sets ${CLAUDE_PLUGIN_ROOT}. In the copy other tools read,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CodiedevConfig } from "../utils";
|
|
2
|
+
import { LocalFile } from "./files";
|
|
3
|
+
export type FileRef = {
|
|
4
|
+
path: string;
|
|
5
|
+
sha256: string;
|
|
6
|
+
executable?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const toRef: (f: {
|
|
9
|
+
path: string;
|
|
10
|
+
sha256: string;
|
|
11
|
+
executable?: boolean;
|
|
12
|
+
}) => FileRef;
|
|
13
|
+
export type SkillFolder = {
|
|
14
|
+
files: LocalFile[];
|
|
15
|
+
notes: string[];
|
|
16
|
+
bytes: number;
|
|
17
|
+
}
|
|
18
|
+
/** A skill folder that can't ship (over the limits): send no list, so the
|
|
19
|
+
* skill keeps its current files, and warn. */
|
|
20
|
+
| {
|
|
21
|
+
skipped: string;
|
|
22
|
+
};
|
|
23
|
+
/** The other files in a SKILL.md's folder, paths relative to that folder.
|
|
24
|
+
* null when the file isn't a skill in its own folder (a repo root or the home
|
|
25
|
+
* folder is never walked). Files that can't ship are skipped with a note, so
|
|
26
|
+
* the folder never fails a push. */
|
|
27
|
+
export declare function skillFolderFiles(skillFile: string, home?: string): SkillFolder | null;
|
|
28
|
+
/** Same files (path, content, executable bit), order-insensitive? */
|
|
29
|
+
export declare function sameFileRefs(a: FileRef[] | undefined, b: FileRef[] | undefined): boolean;
|
|
30
|
+
/** Upload the blobs Codiedev doesn't have yet. `base` is
|
|
31
|
+
* /api/cli/packs/files (admins and managers) or /api/cli/skills/files (anyone).
|
|
32
|
+
* Returns how many were sent. */
|
|
33
|
+
export declare function uploadBlobs(config: CodiedevConfig, files: Array<{
|
|
34
|
+
path: string;
|
|
35
|
+
sha256: string;
|
|
36
|
+
abs: string;
|
|
37
|
+
}>, base: "/api/cli/packs/files" | "/api/cli/skills/files"): Promise<number>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.toRef = void 0;
|
|
37
|
+
exports.skillFolderFiles = skillFolderFiles;
|
|
38
|
+
exports.sameFileRefs = sameFileRefs;
|
|
39
|
+
exports.uploadBlobs = uploadBlobs;
|
|
40
|
+
// A skill's folder files: everything beside SKILL.md (scripts/, docs/, PDFs)
|
|
41
|
+
// travels with the skill. `codiedev push` of a SKILL.md uploads them as blobs
|
|
42
|
+
// and sends the list with the push; packs then ship them under
|
|
43
|
+
// skills/<name>/ automatically. Blobs share the pack-file store (files.ts).
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const shared_1 = require("../commands/shared");
|
|
48
|
+
const files_1 = require("./files");
|
|
49
|
+
const manifest_1 = require("./manifest");
|
|
50
|
+
const toRef = (f) => ({
|
|
51
|
+
path: f.path,
|
|
52
|
+
sha256: f.sha256,
|
|
53
|
+
...(f.executable ? { executable: true } : {}),
|
|
54
|
+
});
|
|
55
|
+
exports.toRef = toRef;
|
|
56
|
+
/** The other files in a SKILL.md's folder, paths relative to that folder.
|
|
57
|
+
* null when the file isn't a skill in its own folder (a repo root or the home
|
|
58
|
+
* folder is never walked). Files that can't ship are skipped with a note, so
|
|
59
|
+
* the folder never fails a push. */
|
|
60
|
+
function skillFolderFiles(skillFile, home = os.homedir()) {
|
|
61
|
+
const abs = path.resolve(skillFile);
|
|
62
|
+
if (path.basename(abs).toLowerCase() !== "skill.md")
|
|
63
|
+
return null;
|
|
64
|
+
// Only a real skill (name: + description:) carries files.
|
|
65
|
+
let data;
|
|
66
|
+
try {
|
|
67
|
+
data = (0, manifest_1.parseFrontmatter)(fs.readFileSync(abs, "utf8")).data;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (!(data.name ?? "").trim() || !(data.description ?? "").trim())
|
|
73
|
+
return null;
|
|
74
|
+
const dir = path.dirname(abs);
|
|
75
|
+
if (dir === path.resolve(home) || dir === path.parse(dir).root || fs.existsSync(path.join(dir, ".git")))
|
|
76
|
+
return null;
|
|
77
|
+
let walked;
|
|
78
|
+
try {
|
|
79
|
+
// Checked as the server does: as if under skills/<name>/ in a pack.
|
|
80
|
+
walked = (0, files_1.collectLocalFiles)([{ dest: ".", source: dir, line: 0 }], dir, home, { lenient: true, checkAs: (p) => `skills/s/${p}` });
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return { skipped: `couldn't read its folder (${error.message})` };
|
|
84
|
+
}
|
|
85
|
+
const files = walked.files.filter((f) => f.path.toLowerCase() !== "skill.md");
|
|
86
|
+
const bytes = files.reduce((n, f) => n + f.size, 0);
|
|
87
|
+
if (files.length > files_1.PACK_MAX_FILES || bytes > files_1.PACK_FILES_MAX_TOTAL_BYTES) {
|
|
88
|
+
return {
|
|
89
|
+
skipped: `its folder is over the limit (${files.length} files, ${(bytes / (1024 * 1024)).toFixed(1)}MB; at most ${files_1.PACK_MAX_FILES} files and ${files_1.PACK_FILES_MAX_TOTAL_BYTES / (1024 * 1024)}MB)`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return { files, notes: walked.notes, bytes };
|
|
93
|
+
}
|
|
94
|
+
/** Same files (path, content, executable bit), order-insensitive? */
|
|
95
|
+
function sameFileRefs(a, b) {
|
|
96
|
+
const key = (l) => JSON.stringify((l ?? []).map((f) => [f.path, f.sha256, !!f.executable]).sort());
|
|
97
|
+
return key(a) === key(b);
|
|
98
|
+
}
|
|
99
|
+
/** Upload the blobs Codiedev doesn't have yet. `base` is
|
|
100
|
+
* /api/cli/packs/files (admins and managers) or /api/cli/skills/files (anyone).
|
|
101
|
+
* Returns how many were sent. */
|
|
102
|
+
async function uploadBlobs(config, files, base) {
|
|
103
|
+
const unique = [...new Map(files.map((f) => [f.sha256, f])).values()];
|
|
104
|
+
if (!unique.length)
|
|
105
|
+
return 0;
|
|
106
|
+
const have = new Set((await (0, shared_1.apiRequest)("POST", `${base}/known`, { config, body: { sha256s: unique.map((f) => f.sha256) } })).known ?? []);
|
|
107
|
+
let sent = 0;
|
|
108
|
+
for (const f of unique) {
|
|
109
|
+
if (have.has(f.sha256))
|
|
110
|
+
continue;
|
|
111
|
+
try {
|
|
112
|
+
await (0, shared_1.apiRequestBytes)("POST", `${base}/upload`, { config, body: fs.readFileSync(f.abs), query: { sha256: f.sha256 } });
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw new Error(`couldn't upload ${f.path} (${error.message})`);
|
|
116
|
+
}
|
|
117
|
+
sent += 1;
|
|
118
|
+
}
|
|
119
|
+
return sent;
|
|
120
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -143,7 +143,7 @@ export declare function ensureAllCodiedevHooksInstalled(): void;
|
|
|
143
143
|
export declare function hasDirectHookPath(configText: string): boolean;
|
|
144
144
|
export declare function migrateHooksToLauncher(): string[];
|
|
145
145
|
export declare function ensureSessionStartHookInstalled(): void;
|
|
146
|
-
export declare const CODIEDEV_INSTRUCTIONS_BODY = "## Codiedev \u2014 team artifact layer\n\nThis machine is connected to Codiedev. Teammates share specs, reviews,\ndecisions, and messages via a CLI on every dev's machine.\n\n**When the user asks to share, push, ping, check messages, or capture a\nthought, use the `codiedev` CLI via Bash:**\n\n| Intent | Command |\n|---|---|\n| \"push this spec\" / \"save this as a skill\" | `codiedev push <file.md>` |\n| \"pull X\" / \"grab the latest spec-X\" / \"what did a teammate push?\" | `codiedev pull <key>` |\n| \"delete X\" / \"remove that test push\" / \"rm spec-old\" | `codiedev delete <key>` (always confirm with the user first) |\n| \"ping a teammate about this\" / \"
|
|
146
|
+
export declare const CODIEDEV_INSTRUCTIONS_BODY = "## Codiedev \u2014 team artifact layer\n\nThis machine is connected to Codiedev. Teammates share specs, reviews,\ndecisions, and messages via a CLI on every dev's machine.\n\n**When the user asks to share, push, ping, check messages, or capture a\nthought, use the `codiedev` CLI via Bash:**\n\n| Intent | Command |\n|---|---|\n| \"push this spec\" / \"save this as a skill\" | `codiedev push <file.md>` |\n| \"pull X\" / \"grab the latest spec-X\" / \"what did a teammate push?\" | `codiedev pull <key>` |\n| \"delete X\" / \"remove that test push\" / \"rm spec-old\" | `codiedev delete <key>` (always confirm with the user first) |\n| \"ping a teammate about this\" / \"send them this spec\" / \"send them this file\" | `codiedev ping <name> \"<msg>\" [--with <key>] [--file <path>]` (`--with` also gives them access) |\n| \"any messages?\" / \"check my inbox\" / \"what did a teammate say?\" | `codiedev inbox` |\n| \"read that reply\" / \"mark that ping read\" | `codiedev read <ping-id>` |\n| \"reply to that\" / \"tell them X\" (answering a message from `codiedev inbox`) | `codiedev reply <ping-id> \"<msg>\" [--file <path>]` (not a new ping: this threads it and marks theirs answered) |\n| \"note that X is a follow-up\" / \"remember X\" | `codiedev note \"<text>\"` |\n| \"share with a teammate\" / \"give someone access\" (no notification) | `codiedev ping <name> --with <key> [--edit]` (no message = silent share) |\n| \"find something about X\" / \"has anyone solved Y?\" | `codiedev search \"<query>\" [--limit N]` |\n| \"show my library\" / \"what artifacts exist?\" | `codiedev library [--scope mine\\|shared\\|all] [--kind doc\\|skill]` |\n| \"run my agent's test cases\" / \"check the dataset on model Y\" / \"run v2 of the dataset\" | `codiedev traces cases <agent> [--dataset <name> [--version <n>]] [--model <id>]` (writes cases + RUN_CASES.md + results.json; follow it, then `codiedev traces report <results.json>`) |\n| \"how are my agents doing?\" / \"what's failing this week?\" / \"show the booking agent's failed runs\" / \"why did run X fail?\" | `codiedev traces overview`; `codiedev traces runs <agent> [--failed] [--flag <issue>] [--days n]`; `codiedev traces run <run> --agent <name>` (add `--json` to read the data yourself) |\n| \"add a helpfulness 1\u20135 score\" / \"make a review queue for tone\" | `codiedev traces score-kinds add <name> --type number --min 1 --max 5`; `codiedev traces queue create <name> --scores <a,b>` |\n| \"score that run 4 for helpfulness\" / \"how is my agent scoring?\" | `codiedev traces score <run> <score> <value> [--agent <name>] [--comment <why>]`; `codiedev traces scores <agent>` |\n| \"put this week's failed runs in the review queue\" | `codiedev traces queue list`; `codiedev traces queue add <queue> --agent <name> [--failed] [--days n]` |\n| \"have a judge score 10% of my agent's runs\" / \"turn off automatic scoring\" | `codiedev traces judge <agent> --on --scores <a,b> --sample 10 [--budget 25]`; `--off`; no flags shows it |\n| \"import these test cases from a CSV\" / \"add an example to the dataset\" / \"save the dataset as a version\" | `codiedev traces import <agent> <file> --dataset <name>`; `codiedev traces add-example <agent> --dataset <name> --input \"\u2026\" [--expected \"\u2026\"]`; `codiedev traces save-version <dataset>`; `codiedev traces versions <dataset>` |\n| \"make a starter pack\" / \"add this to our pack\" / \"package these skills for my team\" | write a `<slug>.pack.md` (start from `codiedev pack export <slug> --manifest --out .` for an existing pack), then `codiedev pack apply <file> [--publish]` \u2014 see **Starter packs** below |\n| \"publish the pack\" / \"ship the pack to my team\" | `codiedev pack publish <slug> [--channel stable\\|beta\\|alpha]` |\n| \"what was the last eval on X?\" / \"file these eval results\" | `codiedev evals last <subject>` before starting a related eval; `codiedev evals report <run.json>` after each variant runs (scores only \u2014 `codiedev evals help` for the shape) |\n\n**Starter packs** (team managers and admins): a pack.md is frontmatter\n(`name:`, `slug:`, optional `description:`) then sections. Ask the user\nwhat to include, write the file, run `codiedev pack apply <file> --dry-run`\nto show them, then apply for real:\n\n```\n---\nname: \"Healthcare scan\"\nslug: \"healthcare-scan\"\n---\n\n# Healthcare scan\n\n## Skills\n\n- ./skills/healthcare-scan\n- lint.md @teammate@example.com\n\n## Files\n\n- runner <- ~/.claude/runner\n```\n\nUnder **Skills**, a local path (`./\u2026`, `~/\u2026` or `/\u2026`) to a skill folder\nor SKILL.md is pushed to the library and added, with the rest of its folder\nshipped as the skill's files; `<key> @<author email>` adds a teammate's\nlibrary skill. **Files** maps local folders to paths in the pack\n(`.` is the pack root). In skills, point at shipped files relative to the\nskill's folder or with `${CLAUDE_PLUGIN_ROOT}/\u2026`. Applying changes the\ndraft; `--publish` (or `codiedev pack publish`) sends it to the team.\n\n**Just push the file \u2014 we'll figure out where it goes.** Every artifact\nis classified into one of two kinds at upload time:\n\n- **skill** \u2014 the file has SKILL.md frontmatter (`name:` + `description:`\n inside a `---` block). Shows on the Skills page in the portal and is\n invokable as a slash command (`/<name>`).\n- **doc** \u2014 everything else. Lives in the library: list it with\n `codiedev library`, fetch it with `codiedev pull <key>`.\n\nDetection is content-based, not filename-based. The filename prefix\n(`spec-` / `decision-` / `proposal-` / `bugfix-` / `review-` /\n`note-`) is preserved as a *tag* you can filter by, but it never gates\nvisibility. A SKILL-frontmatter file named `note-foo.md` still becomes\na skill.\n\nThe `codiedev push` response always says where the artifact landed \u2014\nread it back to the user so they can confirm.\n\n**Resolving \"the spec we just worked on\":**\n- If a file was recently edited in this session matching the conventions\n above, push that file.\n- If content was drafted inline in the conversation, write it to a file\n first (e.g., `/tmp/spec-xxx.md`) then push.\n\n**Teammate names:** first name usually works (`codiedev ping <name> ...`).\nIf ambiguous, the CLI returns candidates \u2014 retry with the full email.\n\n**Search before pushing.** For any \"save this as \u2026\" request, run\n`codiedev search \"<query>\"` first. If prior art exists, pull it and\niterate rather than duplicating.\n\n**One command for messaging and sharing:** `codiedev ping`. A message\nlands in their inbox and emails them; `--with <key>` also gives them read\naccess (`--edit` for edit). With `--with` and no message it's a silent\nshare. (`codiedev send` / `share` still work as aliases.)\n\n**Errors:**\n- \"not connected\" \u2192 user needs to run `npx codiedev connect` with their\n API token from https://codiedev.com/portal/integrations/claude-code\n- \"ambiguous recipient\" \u2192 surface candidates to the user\n";
|
|
147
147
|
/** The full Codiedev instructions (team-skill context + command table), for
|
|
148
148
|
* harnesses Codiedev launches itself (Codie, Codie's pi) rather than a
|
|
149
149
|
* user-owned instructions file. */
|
package/dist/utils.js
CHANGED
|
@@ -1674,13 +1674,12 @@ thought, use the \`codiedev\` CLI via Bash:**
|
|
|
1674
1674
|
| "push this spec" / "save this as a skill" | \`codiedev push <file.md>\` |
|
|
1675
1675
|
| "pull X" / "grab the latest spec-X" / "what did a teammate push?" | \`codiedev pull <key>\` |
|
|
1676
1676
|
| "delete X" / "remove that test push" / "rm spec-old" | \`codiedev delete <key>\` (always confirm with the user first) |
|
|
1677
|
-
| "ping a teammate about this" / "
|
|
1677
|
+
| "ping a teammate about this" / "send them this spec" / "send them this file" | \`codiedev ping <name> "<msg>" [--with <key>] [--file <path>]\` (\`--with\` also gives them access) |
|
|
1678
1678
|
| "any messages?" / "check my inbox" / "what did a teammate say?" | \`codiedev inbox\` |
|
|
1679
1679
|
| "read that reply" / "mark that ping read" | \`codiedev read <ping-id>\` |
|
|
1680
1680
|
| "reply to that" / "tell them X" (answering a message from \`codiedev inbox\`) | \`codiedev reply <ping-id> "<msg>" [--file <path>]\` (not a new ping: this threads it and marks theirs answered) |
|
|
1681
1681
|
| "note that X is a follow-up" / "remember X" | \`codiedev note "<text>"\` |
|
|
1682
|
-
| "share with a teammate" / "give someone access" (no notification) | \`codiedev
|
|
1683
|
-
| "send this to a teammate" / "ask someone to look at this" | \`codiedev send <key> <name> ["<msg>"]\` |
|
|
1682
|
+
| "share with a teammate" / "give someone access" (no notification) | \`codiedev ping <name> --with <key> [--edit]\` (no message = silent share) |
|
|
1684
1683
|
| "find something about X" / "has anyone solved Y?" | \`codiedev search "<query>" [--limit N]\` |
|
|
1685
1684
|
| "show my library" / "what artifacts exist?" | \`codiedev library [--scope mine\\|shared\\|all] [--kind doc\\|skill]\` |
|
|
1686
1685
|
| "run my agent's test cases" / "check the dataset on model Y" / "run v2 of the dataset" | \`codiedev traces cases <agent> [--dataset <name> [--version <n>]] [--model <id>]\` (writes cases + RUN_CASES.md + results.json; follow it, then \`codiedev traces report <results.json>\`) |
|
|
@@ -1756,9 +1755,10 @@ If ambiguous, the CLI returns candidates — retry with the full email.
|
|
|
1756
1755
|
\`codiedev search "<query>"\` first. If prior art exists, pull it and
|
|
1757
1756
|
iterate rather than duplicating.
|
|
1758
1757
|
|
|
1759
|
-
**
|
|
1760
|
-
|
|
1761
|
-
|
|
1758
|
+
**One command for messaging and sharing:** \`codiedev ping\`. A message
|
|
1759
|
+
lands in their inbox and emails them; \`--with <key>\` also gives them read
|
|
1760
|
+
access (\`--edit\` for edit). With \`--with\` and no message it's a silent
|
|
1761
|
+
share. (\`codiedev send\` / \`share\` still work as aliases.)
|
|
1762
1762
|
|
|
1763
1763
|
**Errors:**
|
|
1764
1764
|
- "not connected" → user needs to run \`npx codiedev connect\` with their
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.89",
|
|
4
4
|
"description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codiedev": "dist/cli.js",
|