codiedev 0.9.66 → 0.9.68

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.
@@ -15,6 +15,13 @@ export type CaptureSignals = {
15
15
  skill: string;
16
16
  count: number;
17
17
  }>;
18
+ /** Which skill ARTIFACT an invocation ran, when the loaded skill content
19
+ * carried a `codiedev:artifact=<id>` marker. Lets the server tell two
20
+ * authors' same-named skills apart. Absent when no marker was seen. */
21
+ skillInvocationArtifacts?: Array<{
22
+ skill: string;
23
+ artifactId: string;
24
+ }>;
18
25
  skillPulls: string[];
19
26
  toolCalls: Array<{
20
27
  name: string;
@@ -24,6 +24,17 @@ function parseCaptureSignals(content, source) {
24
24
  let parsedRecords = 0, truncated = false;
25
25
  let premiumRequests, nanoAiu;
26
26
  let cursorSubagents;
27
+ // The skill content a harness loads after an invocation follows it in the
28
+ // transcript; a marker there names the artifact the invocation ran.
29
+ const artifacts = new Map();
30
+ let lastInvoked = null;
31
+ const marker = (text) => {
32
+ if (!lastInvoked || artifacts.has(lastInvoked))
33
+ return;
34
+ const m = text.match(/codiedev:artifact=([a-z0-9]{16,64})/i);
35
+ if (m && artifacts.size < LIMIT)
36
+ artifacts.set(lastInvoked, m[1]);
37
+ };
27
38
  const addSkill = (set, value) => {
28
39
  const key = skillKey(value);
29
40
  if (!key || key.length > 200 || !/^[a-z0-9][a-z0-9_:/.-]*$/i.test(key))
@@ -33,8 +44,10 @@ function parseCaptureSignals(content, source) {
33
44
  return;
34
45
  }
35
46
  set.add(key);
36
- if (set === invocations)
47
+ if (set === invocations) {
37
48
  invocationCounts.set(key, (invocationCounts.get(key) ?? 0) + 1);
49
+ lastInvoked = key;
50
+ }
38
51
  };
39
52
  const command = (value) => {
40
53
  if (typeof value !== "string")
@@ -113,14 +126,19 @@ function parseCaptureSignals(content, source) {
113
126
  for (const part of blocks) {
114
127
  if (part?.type === "tool_use")
115
128
  tool(part.name, part.id, part.input);
129
+ if (part?.type === "tool_result")
130
+ marker(typeof part.content === "string" ? part.content : JSON.stringify(part.content ?? ""));
116
131
  if (part?.type === "text" && e.type === "user") {
117
132
  for (const match of String(part.text ?? "").matchAll(/<command-name>\/?([a-z0-9:_-]+)<\/command-name>/gi))
118
133
  addSkill(invocations, match[1]);
134
+ marker(String(part.text ?? ""));
119
135
  }
120
136
  }
121
- if (typeof blocks === "string" && e.type === "user")
137
+ if (typeof blocks === "string" && e.type === "user") {
122
138
  for (const match of blocks.matchAll(/<command-name>\/?([a-z0-9:_-]+)<\/command-name>/gi))
123
139
  addSkill(invocations, match[1]);
140
+ marker(blocks);
141
+ }
124
142
  if (e.type === "response_item" &&
125
143
  ["function_call", "custom_tool_call"].includes(e.payload?.type))
126
144
  tool(e.payload.name, e.payload.call_id, e.payload.arguments ?? e.payload.input);
@@ -150,6 +168,9 @@ function parseCaptureSignals(content, source) {
150
168
  skillInvocationCounts: [...invocationCounts]
151
169
  .map(([skill, count]) => ({ skill, count }))
152
170
  .sort((a, b) => a.skill.localeCompare(b.skill)),
171
+ ...(artifacts.size
172
+ ? { skillInvocationArtifacts: [...artifacts].map(([skill, artifactId]) => ({ skill, artifactId })).sort((a, b) => a.skill.localeCompare(b.skill)) }
173
+ : {}),
153
174
  skillPulls: [...pulls].sort(),
154
175
  toolCalls: [...tools]
155
176
  .map(([name, calls]) => ({ name, calls }))
package/dist/cli.js CHANGED
@@ -29,6 +29,11 @@ const library_1 = require("./commands/library");
29
29
  const share_1 = require("./commands/share");
30
30
  const send_1 = require("./commands/send");
31
31
  const capture_1 = require("./commands/capture");
32
+ const pack_1 = require("./commands/pack");
33
+ // --- starter packs phase 2 ---
34
+ const hookGate_1 = require("./packs/hookGate");
35
+ const sync_1 = require("./packs/sync");
36
+ // --- end starter packs ---
32
37
  const version_1 = require("./version");
33
38
  const HELP = `
34
39
  Codiedev CLI
@@ -78,6 +83,14 @@ Messaging:
78
83
  codiedev inbox [--unread] [--limit N] Show messages
79
84
  codiedev read <ping-id> Mark read + show full message
80
85
 
86
+ Starter packs:
87
+ codiedev pack list Packs assigned to your team
88
+ codiedev pack install [slug] [--harness claude,codex,copilot,cursor,codie]
89
+ [--dry-run] [--json]
90
+ Install a pack (or all) into every
91
+ detected tool
92
+ codiedev pack status Installed packs, up to date or not
93
+
81
94
  Discovery:
82
95
  codiedev search "<query>" Semantic search across the team's
83
96
  captured sessions and artifacts
@@ -138,6 +151,7 @@ locally before any capture leaves your machine, so an offline or failed portal
138
151
  status sync never weakens it.
139
152
  `.trim();
140
153
  const COMMAND_HELP = {
154
+ pack: pack_1.PACK_HELP,
141
155
  connect: `
142
156
  codiedev connect — link your agent CLI to Codiedev
143
157
 
@@ -394,6 +408,20 @@ function removedCommand(command) {
394
408
  }
395
409
  async function main() {
396
410
  const [, , command, ...rest] = process.argv;
411
+ // --- starter packs: hook-gate runs inside agent hooks; stdout/stderr and
412
+ // the exit code belong to the wrapped command, so nothing else may run. ---
413
+ if (command === "hook-gate") {
414
+ (0, hookGate_1.runHookGateCli)(rest);
415
+ return;
416
+ }
417
+ // Daily "pack has updates" nudge from the cached status (local read only).
418
+ // stderr, so piped/--json output stays clean.
419
+ if (command && !["pack", "packs", "help", "docs", "version", "--version", "-v", "--help", "-h"].includes(command) && !rest.includes("--json")) {
420
+ const nag = (0, sync_1.packNagLine)();
421
+ if (nag)
422
+ console.error(`\n${nag}\n`);
423
+ }
424
+ // --- end starter packs ---
397
425
  if (!command || command === "--help" || command === "-h") {
398
426
  console.log(HELP);
399
427
  return;
@@ -496,6 +524,10 @@ async function main() {
496
524
  case "ignored":
497
525
  await (0, capture_1.runCaptureCommand)([command, ...rest]);
498
526
  return;
527
+ case "pack":
528
+ case "packs":
529
+ await (0, pack_1.runPack)(rest);
530
+ return;
499
531
  case "capture":
500
532
  // Back-compat alias for the earlier `codiedev capture <sub>` form.
501
533
  await (0, capture_1.runCaptureCommand)(rest);
@@ -31,4 +31,10 @@ export declare function inspectCodexHookTrust(configPath: string, hooksPath: str
31
31
  total: number;
32
32
  trusted: number;
33
33
  };
34
+ /** Trust entries for every command handler in a pack's hooks.json. */
35
+ export declare function packCodexHookTrustEntries(hooksPath: string, hooksFile: unknown): CodexHookTrustEntry[];
36
+ /** Regenerate the pack trust block from scratch with exactly `entries`.
37
+ * Entries for packs that were removed or whose hooks changed disappear; the
38
+ * `codiedev-hook-trust` block and all other config are left untouched. */
39
+ export declare function writeCodexPackHookTrust(configPath: string, entries: CodexHookTrustEntry[]): CodexHookTrustEntry[];
34
40
  export {};
@@ -37,6 +37,8 @@ exports.codexCommandHookHash = codexCommandHookHash;
37
37
  exports.codiedevCodexHookTrustEntries = codiedevCodexHookTrustEntries;
38
38
  exports.writeCodexHookTrust = writeCodexHookTrust;
39
39
  exports.inspectCodexHookTrust = inspectCodexHookTrust;
40
+ exports.packCodexHookTrustEntries = packCodexHookTrustEntries;
41
+ exports.writeCodexPackHookTrust = writeCodexPackHookTrust;
40
42
  const crypto = __importStar(require("crypto"));
41
43
  const fs = __importStar(require("fs"));
42
44
  const path = __importStar(require("path"));
@@ -226,3 +228,74 @@ function inspectCodexHookTrust(configPath, hooksPath, hooksFile, requiredSubcomm
226
228
  }).length;
227
229
  return { ok: entries.length > 0 && trusted === entries.length, total: entries.length, trusted };
228
230
  }
231
+ // ---------------------------------------------------------------------------
232
+ // Starter-pack hook trust (SIG-113). `codiedev pack install` is an explicit
233
+ // install action for admin-approved pack hooks, so it trusts every command
234
+ // handler in the installed pack's hooks.json (not only Codiedev's own). These
235
+ // live in their own managed block so `codiedev connect` and `pack install`
236
+ // never clobber each other's entries.
237
+ const PACK_TRUST_BEGIN = "# codiedev-pack-trust:begin";
238
+ const PACK_TRUST_END = "# codiedev-pack-trust:end";
239
+ /** Trust entries for every command handler in a pack's hooks.json. */
240
+ function packCodexHookTrustEntries(hooksPath, hooksFile) {
241
+ const events = hooksFile?.hooks;
242
+ if (!events || typeof events !== "object")
243
+ return [];
244
+ const source = normalizedHooksPath(hooksPath);
245
+ const entries = [];
246
+ for (const [eventName, groupsValue] of Object.entries(events)) {
247
+ const eventLabel = EVENT_LABELS[eventName];
248
+ if (!eventLabel || !Array.isArray(groupsValue))
249
+ continue;
250
+ groupsValue.forEach((groupValue, groupIndex) => {
251
+ const group = groupValue;
252
+ if (!group || !Array.isArray(group.hooks))
253
+ return;
254
+ group.hooks.forEach((handlerValue, handlerIndex) => {
255
+ const handler = handlerValue;
256
+ if (!handler || handler.type !== "command" || typeof handler.command !== "string")
257
+ return;
258
+ entries.push({
259
+ key: `${source}:${eventLabel}:${groupIndex}:${handlerIndex}`,
260
+ eventName,
261
+ command: handler.command,
262
+ currentHash: codexCommandHookHash(eventName, group, handler),
263
+ });
264
+ });
265
+ });
266
+ }
267
+ return entries;
268
+ }
269
+ /** Regenerate the pack trust block from scratch with exactly `entries`.
270
+ * Entries for packs that were removed or whose hooks changed disappear; the
271
+ * `codiedev-hook-trust` block and all other config are left untouched. */
272
+ function writeCodexPackHookTrust(configPath, entries) {
273
+ let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
274
+ existing = existing.replace(new RegExp(`\\n?${PACK_TRUST_BEGIN}\\n[\\s\\S]*?${PACK_TRUST_END}\\n?`, "g"), "\n");
275
+ let lines = existing.split("\n");
276
+ // A stray table for the same key outside our block would make the TOML
277
+ // invalid (duplicate table) — drop it so ours is authoritative.
278
+ for (const entry of entries)
279
+ lines = removeTable(lines, tableHeader(entry.key));
280
+ const base = lines.join("\n").trimEnd();
281
+ if (entries.length === 0) {
282
+ const next = base ? `${base}\n` : "";
283
+ if (!fs.existsSync(configPath) && !next)
284
+ return entries;
285
+ fs.mkdirSync(path.dirname(configPath), { recursive: true, mode: 0o700 });
286
+ fs.writeFileSync(configPath, next, { encoding: "utf8", mode: 0o600 });
287
+ fs.chmodSync(configPath, 0o600);
288
+ return entries;
289
+ }
290
+ const trustBlock = entries.flatMap((entry) => [
291
+ tableHeader(entry.key),
292
+ "enabled = true",
293
+ `trusted_hash = ${JSON.stringify(entry.currentHash)}`,
294
+ "",
295
+ ]).join("\n");
296
+ const next = `${base}${base ? "\n\n" : ""}${PACK_TRUST_BEGIN}\n${trustBlock}${PACK_TRUST_END}\n`;
297
+ fs.mkdirSync(path.dirname(configPath), { recursive: true, mode: 0o700 });
298
+ fs.writeFileSync(configPath, next, { encoding: "utf8", mode: 0o600 });
299
+ fs.chmodSync(configPath, 0o600);
300
+ return entries;
301
+ }
package/dist/codie.js CHANGED
@@ -56,6 +56,8 @@ const codieUsage_1 = require("./codieUsage");
56
56
  const codieRuntime_1 = require("./codieRuntime");
57
57
  const codieRuntimeInstall_1 = require("./codieRuntimeInstall");
58
58
  const upgradeNudge_1 = require("./upgradeNudge");
59
+ const codie_1 = require("./packs/codie");
60
+ const paths_1 = require("./packs/paths");
59
61
  // How long to let the gateway finish booking the last request before asking
60
62
  // what the session cost.
61
63
  const USAGE_SETTLE_MS = 1200;
@@ -97,6 +99,24 @@ function connectedCodiedevConfig() {
97
99
  return {};
98
100
  }
99
101
  }
102
+ // Starter packs (`codiedev pack install`) reach Codie here: skills, agents and
103
+ // the hook shim are merged into the inline config at launch. Best-effort — a
104
+ // broken pack must never stop the harness from starting.
105
+ function withStarterPacks(existing, approved, catalog) {
106
+ try {
107
+ // Agent levels (opus/sonnet/haiku) resolve against this account's catalog.
108
+ const packs = (0, codie_1.codiePackConfig)((0, paths_1.packsRoot)(), { approvedModels: approved, catalog });
109
+ if (process.env.CODIEDEV_DEBUG) {
110
+ for (const note of packs.notes)
111
+ console.error(`codie: starter pack ${note}`);
112
+ }
113
+ return (0, codie_1.mergeCodiePackConfig)(existing, packs);
114
+ }
115
+ catch (error) {
116
+ console.error(`codie: skipping starter packs (${error instanceof Error ? error.message : error})`);
117
+ return existing;
118
+ }
119
+ }
100
120
  function assetPath(name) {
101
121
  // dist/codie.js → ../assets/<name>; src/codie.ts → ../assets/<name>.
102
122
  return path.join(__dirname, "..", "assets", name);
@@ -210,7 +230,7 @@ async function main() {
210
230
  (0, codieBrandInstall_1.installTuiPlugin)();
211
231
  installBrandWordmark(runtime);
212
232
  const config = (0, codieLauncher_1.buildCodieConfig)({
213
- existing: existingInlineConfig(),
233
+ existing: withStarterPacks(existingInlineConfig(), new Set(catalog.map((model) => (0, codieLauncher_1.bareModel)(model.id)).filter(Boolean)), catalog),
214
234
  catalog,
215
235
  env: { ...process.env, ...parsed.overrides, CODIEDEV_GATEWAY_URL: gatewayUrl },
216
236
  plugins: [capturePlugin, metadataPlugin],
@@ -0,0 +1,54 @@
1
+ import { CodiedevConfig } from "../utils";
2
+ import { InstallOptions, PackInstallResult, PackUninstallResult } from "../packs/install";
3
+ import { HarnessName } from "../packs/harnesses";
4
+ import { PackManifest } from "../packs/manifest";
5
+ export declare const DEFAULT_BACKEND_URL = "https://cli.codiedev.com";
6
+ /** CODIEDEV_TOKEN alone is enough: no ~/.codiedev/config.json needed (CI).
7
+ * CODIEDEV_URL defaults to production. Otherwise the connected config. */
8
+ export declare function resolvePackConfig(env?: NodeJS.ProcessEnv): CodiedevConfig | null;
9
+ /** In CI, `--harness` defaults to claude when it's on PATH; anything else is
10
+ * ambiguous on a build box, so say what was found. */
11
+ export declare function ciHarnesses(args: PackArgs, env: NodeJS.ProcessEnv, home?: string): HarnessName[];
12
+ export interface PackArgs {
13
+ sub?: string;
14
+ slug?: string;
15
+ harnesses?: HarnessName[];
16
+ dryRun: boolean;
17
+ json: boolean;
18
+ quiet?: boolean;
19
+ noPrune?: boolean;
20
+ out?: string;
21
+ git?: boolean;
22
+ manifest?: boolean;
23
+ }
24
+ export declare function parsePackArgs(args: string[]): PackArgs;
25
+ export declare function runList(config: CodiedevConfig, json: boolean, overrides?: Partial<InstallOptions>): Promise<void>;
26
+ export interface InstallRun {
27
+ installed: PackInstallResult[];
28
+ removed: PackUninstallResult[];
29
+ /** Installed slugs the server no longer serves (only populated by --dry-run). */
30
+ wouldRemove: string[];
31
+ }
32
+ export declare function runInstall(config: CodiedevConfig, args: PackArgs, overrides?: Partial<InstallOptions>): Promise<InstallRun>;
33
+ export declare function runStatus(config: CodiedevConfig, json: boolean, overrides?: Partial<InstallOptions>, options?: {
34
+ prune?: boolean;
35
+ }): Promise<{
36
+ installed: Array<Record<string, unknown>>;
37
+ removed: PackUninstallResult[];
38
+ notInstalled: string[];
39
+ offline: boolean;
40
+ }>;
41
+ export declare function runPack(args: string[]): Promise<void>;
42
+ /** Init (if needed) and commit everything under `dir`. Uses the user's git
43
+ * identity when configured, a neutral Codiedev one otherwise (fresh CI). */
44
+ export declare function commitExport(dir: string, message: string): string;
45
+ /** Write a CI-committable copy of the pack: `<out>/plugins/<slug>/…` plus
46
+ * `<out>/.claude-plugin/marketplace.json` (existing entries for other packs
47
+ * in the same folder are kept). */
48
+ export declare function exportPluginFolder(out: string, manifest: PackManifest): string;
49
+ export declare function runExport(config: CodiedevConfig, args: PackArgs): Promise<{
50
+ written: string;
51
+ commit?: string;
52
+ }>;
53
+ export declare function runApply(config: CodiedevConfig, args: PackArgs): Promise<Record<string, unknown>>;
54
+ export declare const PACK_HELP = "Usage:\n codiedev pack list Packs assigned to your team\n codiedev pack install [slug] [--harness claude,codex,copilot,cursor,codie] [--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> Create or update a pack from a pack.md\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";