codiedev 0.9.85 → 0.9.87

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/codie.js CHANGED
@@ -50,6 +50,7 @@ const path = __importStar(require("path"));
50
50
  const child_process_1 = require("child_process");
51
51
  const url_1 = require("url");
52
52
  const configDir_1 = require("./configDir");
53
+ const utils_1 = require("./utils");
53
54
  const codieLauncher_1 = require("./codieLauncher");
54
55
  const codieBrandInstall_1 = require("./codieBrandInstall");
55
56
  const codieUsage_1 = require("./codieUsage");
@@ -274,12 +275,23 @@ async function main() {
274
275
  const capturePlugin = (0, codieLauncher_1.resolveCapturePlugin)({ homeDir: os.homedir(), existsSync: fs.existsSync });
275
276
  (0, codieBrandInstall_1.installTuiPlugin)();
276
277
  installBrandWordmark(runtime);
277
- const config = (0, codieLauncher_1.buildCodieConfig)({
278
+ // OpenCode doesn't read the instruction blocks `connect` writes for other
279
+ // tools, so Codie ships its own copy of the Codiedev commands.
280
+ const instructionsFile = path.join(stateDir, "codie-instructions.md");
281
+ try {
282
+ fs.mkdirSync(stateDir, { recursive: true });
283
+ fs.writeFileSync(instructionsFile, (0, utils_1.codiedevInstructionsText)(), "utf8");
284
+ }
285
+ catch (error) {
286
+ console.error(`codie: couldn't write the Codiedev instructions (${error instanceof Error ? error.message : error})`);
287
+ }
288
+ const baseConfig = (0, codieLauncher_1.buildCodieConfig)({
278
289
  existing: withStarterPacks(existingInlineConfig(), new Set(catalog.map((model) => (0, codieLauncher_1.bareModel)(model.id)).filter(Boolean)), catalog),
279
290
  catalog,
280
291
  env: { ...process.env, ...parsed.overrides, CODIEDEV_GATEWAY_URL: gatewayUrl },
281
292
  plugins: [capturePlugin, metadataPlugin],
282
293
  });
294
+ const config = fs.existsSync(instructionsFile) ? (0, codieLauncher_1.withCodiedevInstructions)(baseConfig, instructionsFile) : baseConfig;
283
295
  const env = {
284
296
  ...process.env,
285
297
  ...parsed.overrides,
@@ -33,6 +33,10 @@ export declare function usableModelLimit(value: unknown): ModelLimit | undefined
33
33
  */
34
34
  export declare function withCodieWorkingDirectory(args: string[], cwd: string): string[];
35
35
  export declare function bareModel(value: unknown): string;
36
+ /** Add the Codiedev instructions file to OpenCode's `instructions` (kept
37
+ * alongside the user's own, never duplicated). Codie writes the file itself:
38
+ * OpenCode never sees the instruction blocks `connect` writes for other tools. */
39
+ export declare function withCodiedevInstructions(config: Record<string, any>, file: string): Record<string, any>;
36
40
  export declare function buildCodieConfig({ existing, catalog, env, plugin, plugins, }: {
37
41
  existing?: Record<string, any>;
38
42
  catalog: CatalogModel[];
@@ -47,6 +47,7 @@ exports.usableModelCost = usableModelCost;
47
47
  exports.usableModelLimit = usableModelLimit;
48
48
  exports.withCodieWorkingDirectory = withCodieWorkingDirectory;
49
49
  exports.bareModel = bareModel;
50
+ exports.withCodiedevInstructions = withCodiedevInstructions;
50
51
  exports.buildCodieConfig = buildCodieConfig;
51
52
  exports.stageUsageModule = stageUsageModule;
52
53
  exports.withBrandPlugin = withBrandPlugin;
@@ -140,6 +141,13 @@ function normalizeAgentModels(agents, approved, fallback) {
140
141
  }
141
142
  return normalized;
142
143
  }
144
+ /** Add the Codiedev instructions file to OpenCode's `instructions` (kept
145
+ * alongside the user's own, never duplicated). Codie writes the file itself:
146
+ * OpenCode never sees the instruction blocks `connect` writes for other tools. */
147
+ function withCodiedevInstructions(config, file) {
148
+ const current = Array.isArray(config.instructions) ? config.instructions.filter((x) => typeof x === "string") : [];
149
+ return { ...config, instructions: current.includes(file) ? current : [...current, file] };
150
+ }
143
151
  function buildCodieConfig({ existing = {}, catalog, env = {}, plugin, plugins = [], }) {
144
152
  if (!Array.isArray(catalog) || catalog.length === 0) {
145
153
  throw new Error("No company-approved models were returned by Codiedev");
@@ -21,6 +21,10 @@ export interface PackArgs {
21
21
  out?: string;
22
22
  git?: boolean;
23
23
  manifest?: boolean;
24
+ /** publish / apply --publish: which channel (default stable). */
25
+ channel?: "stable" | "beta" | "alpha";
26
+ /** apply: publish the draft afterwards. */
27
+ publish?: boolean;
24
28
  }
25
29
  export declare function parsePackArgs(args: string[]): PackArgs;
26
30
  /** Download a pack's files (SIG-127) into the blob cache, verified by sha256. */
@@ -54,4 +58,13 @@ export declare function runExport(config: CodiedevConfig, args: PackArgs): Promi
54
58
  commit?: string;
55
59
  }>;
56
60
  export declare function runApply(config: CodiedevConfig, args: PackArgs): Promise<Record<string, unknown>>;
57
- export declare const PACK_HELP = "Usage:\n codiedev pack list Packs assigned to your team\n codiedev pack install [slug] [--harness claude,codex,copilot,cursor,codie,pi] [--dry-run] [--json] [--quiet]\n Install one pack, or every pack that applies\n codiedev pack status [--json] Installed packs and whether they're up to date\n codiedev pack export <slug> --out <dir> [--git] [--manifest]\n Write the pack as a Claude-format plugin folder plus\n .claude-plugin/marketplace.json (commit it for CI).\n --git inits the folder and commits; --manifest writes\n an editable <slug>.pack.md instead\n codiedev pack apply <pack.md> [--dry-run]\n Create or update a pack from a pack.md\n\nFiles (scripts, PDFs, a runner folder): list them under \"## Files\" in pack.md,\none \"- <path in pack> <- <local path>\" per line (local paths are relative to\nthe pack.md; \".\" is the pack root):\n\n ## Files\n\n - runner <- ~/.claude/runner\n - skills/healthcare-scan <- ~/.claude/skills/healthcare-scan\n\n`pack apply` uploads them (admins and team managers; 15MB per file, 50MB per\npack; caches, .git and hidden files are skipped). Without a \"## Files\"\nsection the pack's files are left alone. In skills, point at files relative to\nthe skill's folder, or use ${CLAUDE_PLUGIN_ROOT}/runner/...: Codiedev swaps in the\nreal path for Codex, Copilot, Cursor, pi and Codie.\n\nCursor: packs are copied to ~/.cursor/plugins/local/codiedev-<slug>/. A Cursor\nteam admin can disable \"user local plugin imports\"; then Cursor ignores them.\n\nAuto-update: session starts run `codiedev pack install --quiet` in the background\nabout once an hour. Opt out with CODIEDEV_PACK_AUTOSYNC=0.\n\nHooks for specific skills only: codiedev hook-gate --skills a,b --cmd-b64 <base64url of the command>\n(the pack editor writes this; the command runs via /bin/sh, or cmd.exe on Windows)\n\nCI: set CODIEDEV_TOKEN to a pack CI key (cdvci_\u2026, from the pack's page in the portal).\nNo `codiedev connect` or config file is needed. CI mode (a cdvci_ key, or CI=true)\nnever auto-syncs, reports or prunes, installs the one pack the key can see, defaults\n--harness to claude when it's on PATH, and exits non-zero unless every requested tool\ngot the pack. Skills from a pack are /<pack-slug>:<skill> in Claude Code.\n\n GitLab CI (.gitlab-ci.yml; add CODIEDEV_TOKEN and ANTHROPIC_API_KEY as masked\n CI/CD variables):\n pack-scan:\n image: node:22\n script:\n - npm i -g codiedev @anthropic-ai/claude-code\n - codiedev pack install --harness claude\n - claude -p \"/platform-starter:healthcare-scan\" --output-format text\n\n GitHub Actions (repository secrets CODIEDEV_TOKEN and ANTHROPIC_API_KEY):\n - uses: actions/setup-node@v4\n with: { node-version: 22 }\n - run: npm i -g codiedev @anthropic-ai/claude-code\n - run: codiedev pack install --harness claude\n env:\n CODIEDEV_TOKEN: ${{ secrets.CODIEDEV_TOKEN }}\n - run: claude -p \"/platform-starter:healthcare-scan\" --output-format text\n env:\n ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n\n Or vendor the plugin into the repo instead of installing at build time:\n codiedev pack export <slug> --out .codiedev/packs";
61
+ export interface PublishResult {
62
+ slug: string;
63
+ channel: string;
64
+ version: number;
65
+ unchanged: boolean;
66
+ }
67
+ /** `codiedev pack publish <slug> [--channel stable|beta|alpha]`: snapshot the
68
+ * draft onto a channel. Re-running with no changes publishes nothing. */
69
+ export declare function runPublish(config: CodiedevConfig, args: PackArgs): Promise<PublishResult>;
70
+ 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";
@@ -46,6 +46,7 @@ exports.commitExport = commitExport;
46
46
  exports.exportPluginFolder = exportPluginFolder;
47
47
  exports.runExport = runExport;
48
48
  exports.runApply = runApply;
49
+ exports.runPublish = runPublish;
49
50
  const install_1 = require("../packs/install");
50
51
  const harnesses_1 = require("../packs/harnesses");
51
52
  const manifest_1 = require("../packs/manifest");
@@ -59,6 +60,9 @@ const sync_1 = require("../packs/sync");
59
60
  const harnesses_2 = require("../packs/harnesses");
60
61
  const os = __importStar(require("os"));
61
62
  const utils_1 = require("../utils");
63
+ const push_1 = require("./push");
64
+ const skillMarker_1 = require("../skillMarker");
65
+ const localSkills_1 = require("../packs/localSkills");
62
66
  const shared_1 = require("./shared");
63
67
  const files_1 = require("../packs/files");
64
68
  // --- CI (pack-scoped `cdvci_` keys) ------------------------------------------
@@ -103,8 +107,12 @@ const USAGE = `Usage:
103
107
  .claude-plugin/marketplace.json (commit it for CI).
104
108
  --git inits the folder and commits; --manifest writes
105
109
  an editable <slug>.pack.md instead
106
- codiedev pack apply <pack.md> [--dry-run]
110
+ codiedev pack apply <pack.md> [--publish] [--channel stable|beta|alpha] [--dry-run]
107
111
  Create or update a pack from a pack.md
112
+ (--publish then publishes it, see below)
113
+ codiedev pack publish <slug> [--channel stable|beta|alpha]
114
+ Publish the pack's draft to a channel (default
115
+ stable). Nothing happens when it hasn't changed.
108
116
 
109
117
  Files (scripts, PDFs, a runner folder): list them under "## Files" in pack.md,
110
118
  one "- <path in pack> <- <local path>" per line (local paths are relative to
@@ -156,8 +164,25 @@ got the pack. Skills from a pack are /<pack-slug>:<skill> in Claude Code.
156
164
  env:
157
165
  ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
158
166
 
167
+ Keep a pack up to date from its repo (GitLab; on merges to main). Use a
168
+ personal Codiedev token of a pack owner (portal → Integrations → CLI tokens) as a
169
+ masked CODIEDEV_TOKEN: pack CI keys can only install, not publish.
170
+ pack-release:
171
+ image: node:22
172
+ rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }]
173
+ script:
174
+ - npm i -g codiedev
175
+ - codiedev pack apply packs/platform-starter.pack.md --publish
176
+ Skills listed in pack.md as local folders ("- ./skills/healthcare-scan") are
177
+ pushed when their SKILL.md changed, and the rest of each folder ships with it.
178
+
159
179
  Or vendor the plugin into the repo instead of installing at build time:
160
180
  codiedev pack export <slug> --out .codiedev/packs`;
181
+ function parseChannel(value) {
182
+ if (value === "stable" || value === "beta" || value === "alpha")
183
+ return value;
184
+ throw new Error(`--channel must be stable, beta or alpha (got ${value})`);
185
+ }
161
186
  function parsePackArgs(args) {
162
187
  const out = { dryRun: false, json: false };
163
188
  const positional = [];
@@ -175,6 +200,15 @@ function parsePackArgs(args) {
175
200
  out.git = true;
176
201
  else if (a === "--manifest")
177
202
  out.manifest = true;
203
+ else if (a === "--publish")
204
+ out.publish = true;
205
+ else if (a === "--channel") {
206
+ if (i + 1 >= args.length)
207
+ throw new Error("--channel needs stable, beta or alpha");
208
+ out.channel = parseChannel(args[++i]);
209
+ }
210
+ else if (a.startsWith("--channel="))
211
+ out.channel = parseChannel(a.slice("--channel=".length));
178
212
  else if (a === "--out" || a === "-o") {
179
213
  if (i + 1 >= args.length)
180
214
  throw new Error("--out needs a directory");
@@ -456,7 +490,7 @@ async function runPack(args) {
456
490
  console.error(USAGE);
457
491
  process.exit(1);
458
492
  }
459
- if (!parsed.sub || !["list", "ls", "install", "status", "export", "apply"].includes(parsed.sub)) {
493
+ if (!parsed.sub || !["list", "ls", "install", "status", "export", "apply", "publish"].includes(parsed.sub)) {
460
494
  console.error(USAGE);
461
495
  process.exit(parsed.sub ? 1 : 0);
462
496
  }
@@ -476,6 +510,8 @@ async function runPack(args) {
476
510
  await runExport(config, parsed);
477
511
  else if (parsed.sub === "apply")
478
512
  await runApply(config, parsed);
513
+ else if (parsed.sub === "publish")
514
+ await runPublish(config, parsed);
479
515
  else
480
516
  await runStatus(config, parsed.json, {}, { prune: !parsed.noPrune });
481
517
  });
@@ -634,19 +670,44 @@ async function runApply(config, args) {
634
670
  }
635
671
  if (!markdown.trim())
636
672
  throw new Error(`${file} is empty`);
673
+ const baseDir = path.dirname(file);
674
+ // SIG-128: local skills under `## Skills` are pushed, and a skill folder's
675
+ // other contents ship as that skill's files.
676
+ const skills = (0, localSkills_1.findLocalSkills)(markdown, baseDir);
637
677
  // `## Files` (SIG-127): local folders/files to ship in the plugin dir.
638
- // Absent ⇒ the pack's files are left as they are.
678
+ // Neither a Files section nor a local skill folder ⇒ the pack's files are
679
+ // left as they are; otherwise this list replaces them.
639
680
  const entries = (0, files_1.parseFilesSection)(markdown);
640
- const local = entries ? (0, files_1.collectLocalFiles)(entries, path.dirname(file)) : null;
641
- for (const note of local?.notes ?? [])
681
+ const listed = new Set((entries ?? []).map((e) => e.dest.replace(/^\.\/+/, "").replace(/\/+$/, "").toLowerCase()));
682
+ const folderEntries = (0, localSkills_1.skillFolderEntries)(skills).filter((e) => !listed.has(e.dest.toLowerCase()));
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;
642
690
  console.log(` · ${note}`);
691
+ }
643
692
  if (args.dryRun) {
644
693
  const extra = local ? `, ${local.files.length} file${local.files.length === 1 ? "" : "s"} (${formatBytes(local.files.reduce((n, f) => n + f.size, 0))})` : "";
645
694
  console.log(`Dry run — would apply ${file} (${Buffer.byteLength(markdown)} bytes${extra}).`);
695
+ for (const skill of skills)
696
+ console.log(` skill ${skill.name} ← ${path.relative(baseDir, skill.skillFile) || skill.skillFile} (pushed if changed)`);
646
697
  for (const f of local?.files ?? [])
647
698
  console.log(` ${f.path}`);
648
699
  return {};
649
700
  }
701
+ const keys = new Map();
702
+ let pushedSkills = 0;
703
+ for (const skill of skills) {
704
+ const { key, pushed } = await pushLocalSkill(config, skill);
705
+ keys.set(skill.line, key);
706
+ if (pushed)
707
+ pushedSkills += 1;
708
+ }
709
+ if (keys.size)
710
+ markdown = (0, localSkills_1.rewriteLocalSkills)(markdown, keys);
650
711
  let uploaded = 0;
651
712
  if (local?.files.length)
652
713
  uploaded = await uploadPackFiles(config, local.files);
@@ -675,6 +736,8 @@ async function runApply(config, args) {
675
736
  const verb = res.created === true ? "Created" : res.changed === false ? "No changes to" : "Updated";
676
737
  const version = typeof res.version === "number" ? ` v${res.version}` : "";
677
738
  console.log(`✓ ${verb} ${slug}${version} from ${path.basename(file)}`);
739
+ if (skills.length)
740
+ console.log(` skills: ${skills.length} local (${pushedSkills} pushed, ${skills.length - pushedSkills} unchanged)`);
678
741
  if (local)
679
742
  console.log(` files: ${local.files.length} (${uploaded} uploaded, ${local.files.length - uploaded} already on Codiedev)`);
680
743
  if (Array.isArray(res.warnings)) {
@@ -684,8 +747,74 @@ async function runApply(config, args) {
684
747
  }
685
748
  }
686
749
  }
750
+ if (args.publish && typeof res.slug === "string") {
751
+ const published = await runPublish(config, { ...args, slug: res.slug, json: false });
752
+ if (args.json)
753
+ return { ...res, published };
754
+ }
687
755
  return res;
688
756
  }
757
+ /** `codiedev pack publish <slug> [--channel stable|beta|alpha]`: snapshot the
758
+ * draft onto a channel. Re-running with no changes publishes nothing. */
759
+ async function runPublish(config, args) {
760
+ if (!args.slug)
761
+ throw new Error("Usage: codiedev pack publish <slug> [--channel stable|beta|alpha]");
762
+ if (!(0, manifest_2.isSafeSegment)(args.slug))
763
+ throw new Error(`Not a pack slug: ${args.slug}`);
764
+ const channel = args.channel ?? "stable";
765
+ if (args.dryRun) {
766
+ console.log(`Dry run — would publish ${args.slug}'s draft to ${channel}.`);
767
+ return { slug: args.slug, channel, version: 0, unchanged: true };
768
+ }
769
+ let res;
770
+ try {
771
+ res = await (0, shared_1.apiRequest)("POST", "/api/cli/packs/publish", { config, body: { slug: args.slug, channel } });
772
+ }
773
+ catch (error) {
774
+ const e = error;
775
+ if (e.status === 404)
776
+ throw new Error(`Pack not found: ${args.slug}`);
777
+ if (e.status === 403)
778
+ throw new Error(`You can't publish ${args.slug} (only its owners and admins can)`);
779
+ throw new Error(e.message);
780
+ }
781
+ const out = {
782
+ slug: typeof res.slug === "string" ? res.slug : args.slug,
783
+ channel,
784
+ version: typeof res.version === "number" ? res.version : 0,
785
+ unchanged: res.unchanged === true,
786
+ };
787
+ if (args.json)
788
+ console.log(JSON.stringify(out));
789
+ else if (out.unchanged)
790
+ console.log(`· ${out.slug} v${out.version} is already on ${channel} (no changes to publish)`);
791
+ else
792
+ console.log(`✓ Published ${out.slug} v${out.version} to ${channel}. Teams on ${channel} get it at their next sync.`);
793
+ return out;
794
+ }
795
+ /** Push a local skill unless the library already has this exact text (so CI
796
+ * doesn't add a version on every merge). Team-visible: packs ship to teams. */
797
+ async function pushLocalSkill(config, skill) {
798
+ const local = (0, skillMarker_1.stripSkillMarker)(fs.readFileSync(skill.skillFile, "utf8"));
799
+ try {
800
+ const current = await (0, shared_1.apiRequest)("GET", "/api/cli/pull", { config, query: { key: skill.name } });
801
+ const remote = current.artifact?.markdown;
802
+ if (current.artifact?.kind === "skill" && typeof remote === "string" && (0, skillMarker_1.stripSkillMarker)(remote).trimEnd() === local.trimEnd()) {
803
+ return { key: skill.name, pushed: false };
804
+ }
805
+ }
806
+ catch (error) {
807
+ if (error.status !== 404)
808
+ throw new Error(`couldn't check skill ${skill.name} (${error.message})`);
809
+ }
810
+ try {
811
+ const res = await (0, push_1.pushFile)(config, { absPath: skill.skillFile, key: path.basename(skill.skillFile), kind: "skill", visibility: "team" });
812
+ return { key: res.key ?? skill.name, pushed: true };
813
+ }
814
+ catch (error) {
815
+ throw new Error(`couldn't push skill ${skill.name} (${error.message})`);
816
+ }
817
+ }
689
818
  function formatBytes(n) {
690
819
  return n >= 1024 * 1024 ? `${(n / (1024 * 1024)).toFixed(1)}MB` : n >= 1024 ? `${Math.round(n / 1024)}KB` : `${n}B`;
691
820
  }
package/dist/managedPi.js CHANGED
@@ -230,6 +230,9 @@ function prepareManagedPiDir(opts) {
230
230
  // extensions dropped into the dir go away on every launch.
231
231
  fs.rmSync(path.join(agentDir, "auth.json"), { force: true });
232
232
  fs.rmSync(path.join(agentDir, "extensions"), { recursive: true, force: true });
233
+ // pi reads <agentDir>/AGENTS.md as global context; the managed dir is ours,
234
+ // so the Codiedev instructions are written fresh on every launch.
235
+ fs.writeFileSync(path.join(agentDir, "AGENTS.md"), (0, utils_1.codiedevInstructionsText)(), { mode: 0o600 });
233
236
  const guard = path.join(agentDir, exports.GUARD_FILE);
234
237
  fs.writeFileSync(guard, exports.GUARD_SOURCE, { mode: 0o600 });
235
238
  const capture = path.join(agentDir, exports.CAPTURE_FILE);
@@ -0,0 +1,18 @@
1
+ import { FilesEntry } from "./files";
2
+ export interface LocalSkill {
3
+ /** 1-based line in pack.md. */
4
+ line: number;
5
+ source: string;
6
+ skillFile: string;
7
+ /** The skill folder, when the entry named one (its files ship with the skill). */
8
+ dir: string | null;
9
+ name: string;
10
+ /** `skills/<dir>/` in the plugin, matching the server's skillDirName. */
11
+ pluginDir: string;
12
+ }
13
+ /** Local-path lines under `## Skills` (fenced blocks are skipped). */
14
+ export declare function findLocalSkills(markdown: string, baseDir: string, home?: string): LocalSkill[];
15
+ /** pack.md with each local skill line replaced by `- <key> @me`. */
16
+ export declare function rewriteLocalSkills(markdown: string, keys: Map<number, string>): string;
17
+ /** Files entries for skill folders, so their scripts/docs ship with them. */
18
+ export declare function skillFolderEntries(skills: LocalSkill[]): FilesEntry[];
@@ -0,0 +1,130 @@
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.findLocalSkills = findLocalSkills;
37
+ exports.rewriteLocalSkills = rewriteLocalSkills;
38
+ exports.skillFolderEntries = skillFolderEntries;
39
+ // SIG-128: local skills in a pack.md. Under `## Skills`, a line naming a local
40
+ // path (`./…`, `../…`, `~/…`, `/…`) instead of `<key> @<author email>` is a
41
+ // skill on this machine: `pack apply` pushes it to the library (only when it
42
+ // changed) and rewrites the line to `<key> @me`. A skill FOLDER also ships
43
+ // the rest of its contents (scripts/, docs/…) as the skill's pack files.
44
+ const fs = __importStar(require("fs"));
45
+ const os = __importStar(require("os"));
46
+ const path = __importStar(require("path"));
47
+ const files_1 = require("./files");
48
+ const manifest_1 = require("./manifest");
49
+ const LOCAL = /^(\.{1,2}\/|~\/|~$|\/)/;
50
+ /** Same rule as the server's skillDirName: the frontmatter name when it's a
51
+ * safe folder name, else a slug of it. */
52
+ function pluginDirName(name) {
53
+ if (/^[A-Za-z0-9][A-Za-z0-9._ -]{0,127}$/.test(name) && !name.includes(".."))
54
+ return name;
55
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/g, "");
56
+ return slug || "pack";
57
+ }
58
+ /** Local-path lines under `## Skills` (fenced blocks are skipped). */
59
+ function findLocalSkills(markdown, baseDir, home = os.homedir()) {
60
+ const lines = markdown.replace(/^/, "").split("\n").map((l) => l.replace(/\r$/, ""));
61
+ const out = [];
62
+ let fence = null;
63
+ let inSkills = false;
64
+ for (let i = 0; i < lines.length; i++) {
65
+ const line = lines[i];
66
+ const f = line.match(/^(`{3,})/);
67
+ if (f) {
68
+ if (!fence)
69
+ fence = f[1];
70
+ else if (f[1].length >= fence.length && /^`+\s*$/.test(line))
71
+ fence = null;
72
+ continue;
73
+ }
74
+ if (fence)
75
+ continue;
76
+ const h2 = line.match(/^##\s+(.+?)\s*$/);
77
+ if (h2 && !line.startsWith("###")) {
78
+ inSkills = h2[1].toLowerCase() === "skills";
79
+ continue;
80
+ }
81
+ if (!inSkills)
82
+ continue;
83
+ const m = line.match(/^\s*[-*]\s+(.+?)\s*$/);
84
+ if (!m)
85
+ continue;
86
+ const raw = m[1].replace(/^["']|["']$/g, "");
87
+ if (!LOCAL.test(raw))
88
+ continue;
89
+ const abs = path.resolve(baseDir, (0, files_1.expandHome)(raw, home));
90
+ let stat;
91
+ try {
92
+ stat = fs.statSync(abs);
93
+ }
94
+ catch {
95
+ throw new Error(`pack.md line ${i + 1}: ${raw} doesn't exist`);
96
+ }
97
+ const dir = stat.isDirectory() ? abs : null;
98
+ const skillFile = dir ? path.join(dir, "SKILL.md") : abs;
99
+ if (!fs.existsSync(skillFile))
100
+ throw new Error(`pack.md line ${i + 1}: no SKILL.md in ${raw}`);
101
+ const { data } = (0, manifest_1.parseFrontmatter)(fs.readFileSync(skillFile, "utf8"));
102
+ const name = (data.name ?? "").trim();
103
+ if (!name || !(data.description ?? "").trim()) {
104
+ throw new Error(`pack.md line ${i + 1}: ${path.relative(baseDir, skillFile) || skillFile} needs SKILL.md frontmatter with name: and description:`);
105
+ }
106
+ out.push({ line: i + 1, source: raw, skillFile, dir, name, pluginDir: `skills/${pluginDirName(name)}` });
107
+ }
108
+ const seen = new Map();
109
+ for (const skill of out) {
110
+ const prior = seen.get(skill.name.toLowerCase());
111
+ if (prior)
112
+ throw new Error(`pack.md lines ${prior.line} and ${skill.line} both add a skill named "${skill.name}"`);
113
+ seen.set(skill.name.toLowerCase(), skill);
114
+ }
115
+ return out;
116
+ }
117
+ /** pack.md with each local skill line replaced by `- <key> @me`. */
118
+ function rewriteLocalSkills(markdown, keys) {
119
+ const lines = markdown.split("\n");
120
+ for (const [line, key] of keys) {
121
+ const cr = lines[line - 1].endsWith("\r") ? "\r" : "";
122
+ const indent = lines[line - 1].match(/^\s*/)?.[0] ?? "";
123
+ lines[line - 1] = `${indent}- ${key} @me${cr}`;
124
+ }
125
+ return lines.join("\n");
126
+ }
127
+ /** Files entries for skill folders, so their scripts/docs ship with them. */
128
+ function skillFolderEntries(skills) {
129
+ return skills.filter((s) => s.dir).map((s) => ({ dest: s.pluginDir, source: s.dir, line: s.line }));
130
+ }
package/dist/utils.d.ts CHANGED
@@ -143,6 +143,11 @@ 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\" / \"ask someone for their take\" / \"send them this file\" | `codiedev ping <name> \"<msg>\" [--with <key>] [--file <path>]` |\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 share <key> <name> [--role read\\|edit]` |\n| \"send this to a teammate\" / \"ask someone to look at this\" | `codiedev send <key> <name> [\"<msg>\"]` |\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**Sharing vs sending:** `codiedev share` is silent (access only, no\nnotification). `codiedev send` both grants access AND pings the\nrecipient. Use send when the user is actively looping someone in.\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
+ /** The full Codiedev instructions (team-skill context + command table), for
148
+ * harnesses Codiedev launches itself (Codie, Codie's pi) rather than a
149
+ * user-owned instructions file. */
150
+ export declare function codiedevInstructionsText(): string;
146
151
  export declare function installClaudeCodeInstructions(): void;
147
152
  export declare function installCodexInstructions(): void;
148
153
  export declare function copilotInstructionsPath(): string;
package/dist/utils.js CHANGED
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.DIRECT_UPLOAD_THRESHOLD_CHARS = exports.SWEEP_MIN_AGE_MS = exports.CAPTURE_THROTTLE_MS = exports.FINISH_ORPHAN_AGE_MS = exports.CLAIM_STALE_MS = exports.UPLOAD_TTL_MS = exports.MAX_UPLOAD_ATTEMPTS = exports.PI_EXTENSION_SUBCOMMANDS = exports.CONFIG_PATH = exports.codiedevStateDir = exports.piExtensionPath = exports.inspectPiExtension = void 0;
36
+ exports.DIRECT_UPLOAD_THRESHOLD_CHARS = exports.SWEEP_MIN_AGE_MS = exports.CAPTURE_THROTTLE_MS = exports.FINISH_ORPHAN_AGE_MS = exports.CLAIM_STALE_MS = exports.UPLOAD_TTL_MS = exports.MAX_UPLOAD_ATTEMPTS = exports.CODIEDEV_INSTRUCTIONS_BODY = exports.PI_EXTENSION_SUBCOMMANDS = exports.CONFIG_PATH = exports.codiedevStateDir = exports.piExtensionPath = exports.inspectPiExtension = void 0;
37
37
  exports.readConfig = readConfig;
38
38
  exports.writeConfig = writeConfig;
39
39
  exports.writeConfigAtPath = writeConfigAtPath;
@@ -78,6 +78,7 @@ exports.ensureAllCodiedevHooksInstalled = ensureAllCodiedevHooksInstalled;
78
78
  exports.hasDirectHookPath = hasDirectHookPath;
79
79
  exports.migrateHooksToLauncher = migrateHooksToLauncher;
80
80
  exports.ensureSessionStartHookInstalled = ensureSessionStartHookInstalled;
81
+ exports.codiedevInstructionsText = codiedevInstructionsText;
81
82
  exports.installClaudeCodeInstructions = installClaudeCodeInstructions;
82
83
  exports.installCodexInstructions = installCodexInstructions;
83
84
  exports.copilotInstructionsPath = copilotInstructionsPath;
@@ -1660,7 +1661,7 @@ const CODEX_USER_INSTRUCTIONS_PATH = path.join((0, harnessDirs_1.codexHome)(), "
1660
1661
  const CODEX_CONFIG_PATH = path.join((0, harnessDirs_1.codexHome)(), "config.toml");
1661
1662
  const CODIEDEV_INSTRUCTIONS_BEGIN = "<!-- codiedev-cli:begin -->";
1662
1663
  const CODIEDEV_INSTRUCTIONS_END = "<!-- codiedev-cli:end -->";
1663
- const CODIEDEV_INSTRUCTIONS_BODY = `## Codiedev — team artifact layer
1664
+ exports.CODIEDEV_INSTRUCTIONS_BODY = `## Codiedev — team artifact layer
1664
1665
 
1665
1666
  This machine is connected to Codiedev. Teammates share specs, reviews,
1666
1667
  decisions, and messages via a CLI on every dev's machine.
@@ -1689,8 +1690,41 @@ thought, use the \`codiedev\` CLI via Bash:**
1689
1690
  | "put this week's failed runs in the review queue" | \`codiedev traces queue list\`; \`codiedev traces queue add <queue> --agent <name> [--failed] [--days n]\` |
1690
1691
  | "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 |
1691
1692
  | "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 "…" [--expected "…"]\`; \`codiedev traces save-version <dataset>\`; \`codiedev traces versions <dataset>\` |
1693
+ | "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]\` — see **Starter packs** below |
1694
+ | "publish the pack" / "ship the pack to my team" | \`codiedev pack publish <slug> [--channel stable\\|beta\\|alpha]\` |
1692
1695
  | "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 — \`codiedev evals help\` for the shape) |
1693
1696
 
1697
+ **Starter packs** (team managers and admins): a pack.md is frontmatter
1698
+ (\`name:\`, \`slug:\`, optional \`description:\`) then sections. Ask the user
1699
+ what to include, write the file, run \`codiedev pack apply <file> --dry-run\`
1700
+ to show them, then apply for real:
1701
+
1702
+ \`\`\`
1703
+ ---
1704
+ name: "Healthcare scan"
1705
+ slug: "healthcare-scan"
1706
+ ---
1707
+
1708
+ # Healthcare scan
1709
+
1710
+ ## Skills
1711
+
1712
+ - ./skills/healthcare-scan
1713
+ - lint.md @teammate@example.com
1714
+
1715
+ ## Files
1716
+
1717
+ - runner <- ~/.claude/runner
1718
+ \`\`\`
1719
+
1720
+ Under **Skills**, a local path (\`./…\`, \`~/…\` or \`/…\`) to a skill folder
1721
+ or SKILL.md is pushed to the library and added, with the rest of its folder
1722
+ shipped as the skill's files; \`<key> @<author email>\` adds a teammate's
1723
+ library skill. **Files** maps local folders to paths in the pack
1724
+ (\`.\` is the pack root). In skills, point at shipped files relative to the
1725
+ skill's folder or with \`\${CLAUDE_PLUGIN_ROOT}/…\`. Applying changes the
1726
+ draft; \`--publish\` (or \`codiedev pack publish\`) sends it to the team.
1727
+
1694
1728
  **Just push the file — we'll figure out where it goes.** Every artifact
1695
1729
  is classified into one of two kinds at upload time:
1696
1730
 
@@ -1731,13 +1765,19 @@ recipient. Use send when the user is actively looping someone in.
1731
1765
  API token from https://codiedev.com/portal/integrations/claude-code
1732
1766
  - "ambiguous recipient" → surface candidates to the user
1733
1767
  `;
1768
+ /** The full Codiedev instructions (team-skill context + command table), for
1769
+ * harnesses Codiedev launches itself (Codie, Codie's pi) rather than a
1770
+ * user-owned instructions file. */
1771
+ function codiedevInstructionsText() {
1772
+ return `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${exports.CODIEDEV_INSTRUCTIONS_BODY}\n`;
1773
+ }
1734
1774
  /**
1735
1775
  * Write the codiedev agent-facing docs block to the user-level Claude
1736
1776
  * instructions file so Claude Code picks up the commands in every session
1737
1777
  * (no per-repo CLAUDE.md needed). Idempotent — replaces the existing block
1738
1778
  * between its begin/end markers on re-run.
1739
1779
  */
1740
- function writeInstructionsBlock(targetPath, body = CODIEDEV_INSTRUCTIONS_BODY) {
1780
+ function writeInstructionsBlock(targetPath, body = exports.CODIEDEV_INSTRUCTIONS_BODY) {
1741
1781
  const dir = path.dirname(targetPath);
1742
1782
  if (!fs.existsSync(dir)) {
1743
1783
  fs.mkdirSync(dir, { recursive: true });
@@ -1784,10 +1824,10 @@ function piInstructionsPath() {
1784
1824
  /** pi has no session-start output channel, so team skill context rides in
1785
1825
  * AGENTS.md the way it does for Copilot. */
1786
1826
  function installPiInstructions() {
1787
- writeInstructionsBlock(piInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`);
1827
+ writeInstructionsBlock(piInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${exports.CODIEDEV_INSTRUCTIONS_BODY}`);
1788
1828
  }
1789
1829
  function installCopilotInstructions() {
1790
- writeInstructionsBlock(copilotInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`);
1830
+ writeInstructionsBlock(copilotInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${exports.CODIEDEV_INSTRUCTIONS_BODY}`);
1791
1831
  }
1792
1832
  // Cursor uses .mdc rule files with YAML frontmatter (description, alwaysApply,
1793
1833
  // globs). `.cursorrules` is deprecated and ignored by Agent mode as of 2026.
@@ -1804,7 +1844,7 @@ function installCursorInstructions() {
1804
1844
  const block = CURSOR_INSTRUCTIONS_FRONTMATTER +
1805
1845
  CODIEDEV_INSTRUCTIONS_BEGIN +
1806
1846
  "\n" +
1807
- `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}` +
1847
+ `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${exports.CODIEDEV_INSTRUCTIONS_BODY}` +
1808
1848
  "\n" +
1809
1849
  CODIEDEV_INSTRUCTIONS_END +
1810
1850
  "\n";
@@ -1822,7 +1862,7 @@ function installCursorInstructions() {
1822
1862
  existing.slice(0, beginIdx) +
1823
1863
  CODIEDEV_INSTRUCTIONS_BEGIN +
1824
1864
  "\n" +
1825
- `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}` +
1865
+ `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${exports.CODIEDEV_INSTRUCTIONS_BODY}` +
1826
1866
  "\n" +
1827
1867
  CODIEDEV_INSTRUCTIONS_END +
1828
1868
  existing.slice(endIdx + CODIEDEV_INSTRUCTIONS_END.length);
@@ -1833,10 +1873,10 @@ function installCursorInstructions() {
1833
1873
  fs.writeFileSync(CURSOR_RULES_PATH, next, "utf8");
1834
1874
  }
1835
1875
  function installedInstructionsTargets() {
1836
- const withTeamContext = `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`;
1876
+ const withTeamContext = `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${exports.CODIEDEV_INSTRUCTIONS_BODY}`;
1837
1877
  return [
1838
- { path: CLAUDE_USER_INSTRUCTIONS_PATH, body: CODIEDEV_INSTRUCTIONS_BODY },
1839
- { path: CODEX_USER_INSTRUCTIONS_PATH, body: CODIEDEV_INSTRUCTIONS_BODY },
1878
+ { path: CLAUDE_USER_INSTRUCTIONS_PATH, body: exports.CODIEDEV_INSTRUCTIONS_BODY },
1879
+ { path: CODEX_USER_INSTRUCTIONS_PATH, body: exports.CODIEDEV_INSTRUCTIONS_BODY },
1840
1880
  { path: copilotInstructionsPath(), body: withTeamContext },
1841
1881
  { path: CURSOR_RULES_PATH, body: withTeamContext },
1842
1882
  { path: piInstructionsPath(), body: withTeamContext },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.85",
3
+ "version": "0.9.87",
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",