codiedev 0.9.25 → 0.9.28

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # codiedev
2
2
 
3
- Connect Claude Code or Codex to Codiedev for org-wide session capture.
3
+ Connect Claude Code, Codex, Cursor, or OpenCode to Codiedev for team session capture.
4
4
 
5
5
  ## Usage
6
6
 
@@ -12,21 +12,49 @@ The CLI will prompt you for an API token (get one at https://codiedev.com/portal
12
12
 
13
13
  - **Claude Code** — `SessionEnd` hook in `~/.claude/settings.json`
14
14
  - **Codex** — `Stop` hook in `~/.codex/hooks.json`
15
+ - **Cursor** — `sessionEnd` hook in `~/.cursor/hooks.json`
16
+ - **OpenCode / Codie** — plugin in `~/.config/opencode/plugins/codiedev.js`
15
17
 
16
- If both are installed, hooks are wired up for both. If neither is installed, the config is saved and you can re-run `connect` after installing one.
18
+ Installed clients are wired automatically. The OpenCode adapter is staged even
19
+ when OpenCode is not installed yet, so adding the Codie harness later does not
20
+ require a second connect step.
17
21
 
18
22
  ## What gets captured
19
23
 
20
- - Only sessions inside repos your workspace already tracks
24
+ - Agent sessions from connected devices (capture is enabled by default)
21
25
  - Transcript + basic stats (message count, tool calls)
22
26
  - Processed into summaries and embeddings server-side for search in `/portal/memories`
23
27
 
24
- Each captured memory records its `source` (`claude-code` or `codex`) so you can filter in the portal.
28
+ Each captured memory records its source (`claude-code`, `codex`, `cursor`, or
29
+ `opencode`) so the portal can label the client without breaking the shared
30
+ session → repository / branch / SHA → merge attribution model.
31
+
32
+ ## Keep folders local
33
+
34
+ Folder exclusions are device-specific and include every child folder. The CLI
35
+ writes the rule locally before attempting to sync its read-only status to the
36
+ portal, so an offline or failed status sync never weakens the exclusion.
37
+
38
+ ```bash
39
+ codiedev ignore ~/Obsidian
40
+ codiedev ignored ~/Obsidian/People # check whether a path is ignored
41
+ codiedev ignored # list every ignored folder
42
+ codiedev unignore ~/Obsidian
43
+ ```
44
+
45
+ Run `ignore` once per device or persistent VM/container. A new ephemeral
46
+ container needs its own rule unless `~/.codiedev/config.json` is persisted.
25
47
 
26
48
  ## Codex note
27
49
 
28
50
  Codex doesn't currently expose a `SessionEnd` event, so the hook runs on `Stop` (fires after each assistant turn). The backend dedupes by `(company, session_id)` and replaces the stored transcript on re-upload so the latest turn's snapshot wins. Summarization happens once per session; subsequent turn uploads keep the raw transcript fresh but don't re-summarize (to avoid running up LLM cost).
29
51
 
52
+ ## OpenCode note
53
+
54
+ OpenCode exposes session lifecycle events through plugins. Codiedev snapshots
55
+ the SDK message list on `session.idle`, normalizes it into the shared capture
56
+ pipeline, and lets the server close the session after a quiet-period lease.
57
+
30
58
  ## Config
31
59
 
32
60
  Written to `~/.codiedev/config.json`. Override the backend with `CODIEDEV_URL` if needed.
@@ -0,0 +1,54 @@
1
+ // Metadata-only attribution for the Codiedev OpenCode distribution.
2
+ // This plugin never reads or sends prompts, messages, code, diffs, or tool
3
+ // output. It only attaches stable session/git identifiers and explicit skill
4
+ // names to inference requests handled by the company gateway.
5
+
6
+ const skillUseBySession = new Map();
7
+
8
+ function clean(value, max) {
9
+ const text = typeof value === "string" ? value.trim() : "";
10
+ return text ? text.slice(0, max) : "";
11
+ }
12
+
13
+ function addSkill(sessionID, value) {
14
+ const skill = clean(value, 160).replace(/^\//, "").replace(/\.md$/i, "");
15
+ if (!skill) return;
16
+ const skills = skillUseBySession.get(sessionID) || new Set();
17
+ if (skills.size < 50) skills.add(skill);
18
+ skillUseBySession.set(sessionID, skills);
19
+ }
20
+
21
+ export const CodiedevMetadata = async () => ({
22
+ "command.execute.before": async (input) => {
23
+ // Built-in commands are UI actions. Named custom commands are explicit
24
+ // skill dispatches and become part of the session attribution spine.
25
+ const builtIns = new Set([
26
+ "connect", "help", "models", "new", "share", "unshare", "undo", "redo",
27
+ "compact", "sessions", "timeline", "themes", "editor", "export", "exit",
28
+ ]);
29
+ if (!builtIns.has(input.command)) addSkill(input.sessionID, input.command);
30
+ },
31
+ "tool.execute.before": async (input, output) => {
32
+ if (input.tool !== "skill") return;
33
+ addSkill(input.sessionID, output.args?.name || output.args?.skill || output.args?.key);
34
+ },
35
+ "chat.headers": async (input, output) => {
36
+ output.headers["X-Codiedev-Session-Id"] = clean(input.sessionID, 200);
37
+ const repo = clean(process.env.CODIEDEV_REPO_URL, 2048);
38
+ const branch = clean(process.env.CODIEDEV_BRANCH, 200);
39
+ const sha = clean(process.env.CODIEDEV_HEAD_SHA, 40);
40
+ const user = clean(process.env.CODIEDEV_USER, 200);
41
+ const skills = [...(skillUseBySession.get(input.sessionID) || [])].join(",");
42
+ if (repo) output.headers["X-Codiedev-Repo-Url"] = repo;
43
+ if (branch) output.headers["X-Codiedev-Branch"] = branch;
44
+ if (/^[0-9a-f]{7,40}$/i.test(sha)) output.headers["X-Codiedev-Head-Sha"] = sha;
45
+ if (user) output.headers["X-Codiedev-User"] = user;
46
+ if (skills) output.headers["X-Codiedev-Skills-Used"] = skills;
47
+ },
48
+ event: async ({ event }) => {
49
+ if (event?.type === "session.deleted") {
50
+ const sessionID = event.properties?.info?.id || event.properties?.sessionID;
51
+ if (sessionID) skillUseBySession.delete(sessionID);
52
+ }
53
+ },
54
+ });
@@ -0,0 +1,63 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ // Codiedev branding for the OpenCode home screen: the intertwined c/d brand
3
+ // mark (rectangle crop, sized to the footprint the stock wordmark occupied)
4
+ // over the product name, with a "powered by opencode" credit line beneath.
5
+ // Installed and kept current by `codie`; deleting this file and its tui.json
6
+ // entry restores the stock logo.
7
+ // Mark ported from the retired Grok-fork TUI (assets/logo/logo07.txt), itself
8
+ // derived from vm-demo/public/codiedev-icon.png — keep aligned to that
9
+ // intertwined c/d silhouette when refreshing brand assets.
10
+ import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
11
+ import { TextAttributes } from "@opentui/core"
12
+
13
+ const MARK = [
14
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿",
15
+ "⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⣀⣀⣀⡀⣿⣿⣿",
16
+ "⠀⣠⣾⣿⣿⣿⣿⣿⣶⠄⢠⣶⣿⣿⣿⣿⣿⣿⣿⣿",
17
+ "⣼⣿⣿⠟⠉⠈⠉⠛⠁⣰⣿⣿⡟⠉⠁⠉⠻⣿⣿⣿",
18
+ "⣿⣿⣿⠀⠀⠀⠀⢀⣼⣿⣿⣿⡀⠀⠀⠀⠀⣿⣿⣿",
19
+ "⠹⣿⣿⣷⣤⣤⣴⣿⣿⡿⢿⣿⣷⣤⣤⣤⣾⣿⣿⠏",
20
+ "⠀⠈⠻⢿⣿⣿⣿⠿⠋⠀⠀⠙⠿⣿⣿⣿⡿⠟⠁",
21
+ ]
22
+
23
+ const tui: TuiPlugin = async (api) => {
24
+ api.slots.register({
25
+ slots: {
26
+ home_logo(ctx) {
27
+ const theme = ctx.theme.current
28
+ return (
29
+ <box flexDirection="column" alignItems="center" gap={1}>
30
+ <box flexDirection="column">
31
+ {MARK.map((line) => (
32
+ <text fg={theme.primary} selectable={false}>
33
+ {line}
34
+ </text>
35
+ ))}
36
+ </box>
37
+ <box flexDirection="column" alignItems="center">
38
+ <box flexDirection="row">
39
+ <text fg={theme.text} attributes={TextAttributes.BOLD} selectable={false}>
40
+ codiedev
41
+ </text>
42
+ <text fg={theme.textMuted} selectable={false}>
43
+ {" "}
44
+ harness
45
+ </text>
46
+ </box>
47
+ <text fg={theme.textMuted} selectable={false}>
48
+ powered by opencode
49
+ </text>
50
+ </box>
51
+ </box>
52
+ )
53
+ },
54
+ },
55
+ })
56
+ }
57
+
58
+ const plugin: TuiPluginModule & { id: string } = {
59
+ id: "codiedev.brand",
60
+ tui,
61
+ }
62
+
63
+ export default plugin
@@ -38,9 +38,9 @@ exports.loadCache = loadCache;
38
38
  exports.saveCache = saveCache;
39
39
  exports.applyCache = applyCache;
40
40
  const fs = __importStar(require("fs"));
41
- const os = __importStar(require("os"));
42
41
  const path = __importStar(require("path"));
43
- const CACHE_PATH = path.join(os.homedir(), ".codiedev", "backup-state.json");
42
+ const configDir_1 = require("../configDir");
43
+ const CACHE_PATH = path.join((0, configDir_1.codiedevStateDir)(), "backup-state.json");
44
44
  function cacheKey(repoKey, rel) {
45
45
  return `${repoKey}::${rel}`;
46
46
  }
@@ -86,6 +86,7 @@ const child_process_1 = require("child_process");
86
86
  const utils_1 = require("../utils");
87
87
  const cache_2 = require("./cache");
88
88
  const push = __importStar(require("../commands/push"));
89
+ const captureExclusions_1 = require("../captureExclusions");
89
90
  function gitRoot(cwd) {
90
91
  try {
91
92
  return (0, child_process_1.execFileSync)("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
@@ -143,6 +144,8 @@ function runBackup(cwd, hookScriptPath) {
143
144
  const config = (0, utils_1.readConfig)();
144
145
  if (!config)
145
146
  return;
147
+ if ((0, captureExclusions_1.isCaptureIgnored)(cwd, config.captureIgnoredPaths))
148
+ return;
146
149
  const root = gitRoot(cwd);
147
150
  if (!root)
148
151
  return;
@@ -195,6 +198,8 @@ async function runBackupUpload(jobFile, deps = DEFAULT_BACKUP_UPLOAD_DEPS) {
195
198
  const config = deps.readConfig(); // same machine — read full config from disk
196
199
  if (!config)
197
200
  return;
201
+ if ((0, captureExclusions_1.isCaptureIgnored)(job.root, config.captureIgnoredPaths))
202
+ return;
198
203
  const cache = deps.loadCache();
199
204
  let dirty = false;
200
205
  for (const f of job.files) {
@@ -0,0 +1,30 @@
1
+ import type { CodiedevConfig } from "./utils";
2
+ export interface NormalizeCapturePathDeps {
3
+ cwd?: () => string;
4
+ homedir?: () => string;
5
+ realpathSync?: (value: string) => string;
6
+ }
7
+ /**
8
+ * Convert a user-entered path into the canonical absolute form persisted in
9
+ * ~/.codiedev/config.json. Existing symlinks resolve to their real target so a
10
+ * session cannot bypass an exclusion by entering the same folder through a
11
+ * different alias. Non-existent paths still normalize deterministically.
12
+ */
13
+ export declare function normalizeCapturePath(input: string, deps?: NormalizeCapturePathDeps): string;
14
+ export declare function findMatchingCaptureExclusion(candidate: string | string[] | undefined, ignoredPaths: string[] | undefined): string | null;
15
+ export declare function isCaptureIgnored(candidate: string | string[] | undefined, ignoredPaths: string[] | undefined): boolean;
16
+ export interface CaptureScopeInput {
17
+ cwd?: string;
18
+ workspace_roots?: string[];
19
+ }
20
+ /** Every local root associated with a hook event, deduped in stable order. */
21
+ export declare function captureScopePaths(input: CaptureScopeInput, fallback?: string): string[];
22
+ export declare function shouldIgnoreCaptureInput(config: Pick<CodiedevConfig, "captureIgnoredPaths"> | null | undefined, input: CaptureScopeInput, fallback?: string): boolean;
23
+ /**
24
+ * Final egress decision for queued transcript uploads. Jobs written by older
25
+ * CLI versions have no scope metadata; once any exclusion exists, dropping
26
+ * those legacy jobs is safer than guessing that they are public.
27
+ */
28
+ export declare function shouldSkipCaptureUploadJob(job: {
29
+ scopePaths?: string[];
30
+ }, config: Pick<CodiedevConfig, "captureIgnoredPaths"> | null | undefined): boolean;
@@ -0,0 +1,118 @@
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.normalizeCapturePath = normalizeCapturePath;
37
+ exports.findMatchingCaptureExclusion = findMatchingCaptureExclusion;
38
+ exports.isCaptureIgnored = isCaptureIgnored;
39
+ exports.captureScopePaths = captureScopePaths;
40
+ exports.shouldIgnoreCaptureInput = shouldIgnoreCaptureInput;
41
+ exports.shouldSkipCaptureUploadJob = shouldSkipCaptureUploadJob;
42
+ const fs = __importStar(require("fs"));
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ const DEFAULT_NORMALIZE_DEPS = {
46
+ cwd: () => process.cwd(),
47
+ homedir: () => os.homedir(),
48
+ realpathSync: (value) => fs.realpathSync.native(value),
49
+ };
50
+ /**
51
+ * Convert a user-entered path into the canonical absolute form persisted in
52
+ * ~/.codiedev/config.json. Existing symlinks resolve to their real target so a
53
+ * session cannot bypass an exclusion by entering the same folder through a
54
+ * different alias. Non-existent paths still normalize deterministically.
55
+ */
56
+ function normalizeCapturePath(input, deps = {}) {
57
+ const d = { ...DEFAULT_NORMALIZE_DEPS, ...deps };
58
+ const trimmed = input.trim();
59
+ if (!trimmed)
60
+ throw new Error("Folder path is required");
61
+ const expanded = /^~(?=$|[\\/])/.test(trimmed)
62
+ ? path.join(d.homedir(), trimmed.slice(1))
63
+ : trimmed;
64
+ const absolute = path.resolve(d.cwd(), expanded);
65
+ try {
66
+ return path.normalize(d.realpathSync(absolute));
67
+ }
68
+ catch {
69
+ return path.normalize(absolute);
70
+ }
71
+ }
72
+ function comparisonKey(value) {
73
+ const normalized = path.normalize(value);
74
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
75
+ }
76
+ function containsPath(root, candidate) {
77
+ const relative = path.relative(comparisonKey(root), comparisonKey(candidate));
78
+ return (relative === "" ||
79
+ (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative)));
80
+ }
81
+ function findMatchingCaptureExclusion(candidate, ignoredPaths) {
82
+ if (!candidate || !ignoredPaths || ignoredPaths.length === 0)
83
+ return null;
84
+ const candidates = Array.isArray(candidate) ? candidate : [candidate];
85
+ for (const ignored of ignoredPaths) {
86
+ const root = normalizeCapturePath(ignored);
87
+ for (const value of candidates) {
88
+ if (containsPath(root, normalizeCapturePath(value)))
89
+ return root;
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ function isCaptureIgnored(candidate, ignoredPaths) {
95
+ return findMatchingCaptureExclusion(candidate, ignoredPaths) !== null;
96
+ }
97
+ /** Every local root associated with a hook event, deduped in stable order. */
98
+ function captureScopePaths(input, fallback = process.cwd()) {
99
+ const values = [input.cwd, ...(input.workspace_roots ?? [])]
100
+ .filter((value) => typeof value === "string" && value.length > 0);
101
+ return [...new Set(values.length > 0 ? values : [fallback])];
102
+ }
103
+ function shouldIgnoreCaptureInput(config, input, fallback) {
104
+ return isCaptureIgnored(captureScopePaths(input, fallback), config?.captureIgnoredPaths);
105
+ }
106
+ /**
107
+ * Final egress decision for queued transcript uploads. Jobs written by older
108
+ * CLI versions have no scope metadata; once any exclusion exists, dropping
109
+ * those legacy jobs is safer than guessing that they are public.
110
+ */
111
+ function shouldSkipCaptureUploadJob(job, config) {
112
+ const ignoredPaths = config?.captureIgnoredPaths ?? [];
113
+ if (ignoredPaths.length === 0)
114
+ return false;
115
+ if (!job.scopePaths || job.scopePaths.length === 0)
116
+ return true;
117
+ return isCaptureIgnored(job.scopePaths, ignoredPaths);
118
+ }
@@ -36,7 +36,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.CAPTURE_LOG_PATH = exports.CAPTURE_LOG_MAX_BYTES = void 0;
37
37
  exports.appendCaptureLog = appendCaptureLog;
38
38
  exports.readCaptureLogTail = readCaptureLogTail;
39
- // Rotating capture diagnostic log at ~/.codiedev/capture.log.
39
+ // Rotating capture diagnostic log at <codiedevStateDir()>/capture.log
40
+ // (~/.codiedev by default; ~/.codiedev-desktop under CODIEDEV_CONFIG_DIR).
40
41
  //
41
42
  // Every stage of the capture pipeline (capture-finish child, upload child,
42
43
  // stderr from detached children) writes one line here so users — and
@@ -45,12 +46,14 @@ exports.readCaptureLogTail = readCaptureLogTail;
45
46
  // best-effort throughout: appendCaptureLog NEVER throws into the hook path.
46
47
  const fs = __importStar(require("fs"));
47
48
  const path = __importStar(require("path"));
48
- const os = __importStar(require("os"));
49
+ // Imported from configDir (not utils) — utils.ts imports this module, so
50
+ // importing utils.ts here would create a circular import.
51
+ const configDir_1 = require("./configDir");
49
52
  // Rotate the log when it exceeds this size (keeps the newest ~half). 256 KB
50
53
  // holds thousands of entries; a user would have to see hundreds of errors
51
54
  // before hitting this limit, so it's a generous safety valve.
52
55
  exports.CAPTURE_LOG_MAX_BYTES = 256 * 1024;
53
- exports.CAPTURE_LOG_PATH = path.join(os.homedir(), ".codiedev", "capture.log");
56
+ exports.CAPTURE_LOG_PATH = path.join((0, configDir_1.codiedevStateDir)(), "capture.log");
54
57
  // Append one structured line to the capture log. Rotates the file when it
55
58
  // grows past CAPTURE_LOG_MAX_BYTES by truncating it to the newest half (keeps
56
59
  // the most recent diagnostics). Best-effort: NEVER throws into the hook path —
package/dist/cli.js CHANGED
@@ -33,6 +33,7 @@ const post_1 = require("./commands/post");
33
33
  const share_1 = require("./commands/share");
34
34
  const send_1 = require("./commands/send");
35
35
  const react_1 = require("./commands/react");
36
+ const capture_1 = require("./commands/capture");
36
37
  const version_1 = require("./version");
37
38
  const HELP = `
38
39
  Codiedev CLI
@@ -45,6 +46,13 @@ Connect:
45
46
  codiedev connect Link Claude Code / Codex to your org
46
47
  codiedev doctor Verify your setup after connecting
47
48
 
49
+ Harness:
50
+ codie Launch the governed Codie harness.
51
+ Same install, same token as above —
52
+ use your own agent (Claude Code,
53
+ Codex, Cursor) or Codie, your call.
54
+ The engine downloads on first run.
55
+
48
56
  Artifacts:
49
57
  codiedev push <file.md> Author or update an artifact
50
58
  codiedev pull <key> [--version N] [--out path]
@@ -95,6 +103,11 @@ Feed:
95
103
  [--mention <name>]... Publish to the team feed
96
104
  codiedev react <postId> <emoji> Toggle a reaction on a feed post
97
105
 
106
+ Privacy:
107
+ codiedev ignore <path> Never capture this folder or its children
108
+ codiedev unignore <path> Stop ignoring a folder
109
+ codiedev ignored [path] List ignored folders, or check one path
110
+
98
111
  Capture:
99
112
  codiedev note "<text>" Capture a passing thought
100
113
 
@@ -130,6 +143,18 @@ Backend: ${process.env.CODIEDEV_URL ?? "https://codiedev.com"}
130
143
  Token: https://codiedev.com/portal/integrations/claude-code
131
144
  Docs: codiedev docs
132
145
  `.trim();
146
+ const IGNORE_HELP = `
147
+ codiedev ignore — keep folders out of session capture
148
+
149
+ codiedev ignore <path> Never capture this folder or its children
150
+ codiedev unignore <path> Stop ignoring a folder
151
+ codiedev ignored List ignored folders on this device
152
+ codiedev ignored <path> Check whether a specific path is ignored
153
+
154
+ Exclusions are per-device and cover every child folder. The rule is written
155
+ locally before any capture leaves your machine, so an offline or failed portal
156
+ status sync never weakens it.
157
+ `.trim();
133
158
  const COMMAND_HELP = {
134
159
  connect: `
135
160
  codiedev connect — link your agent CLI to Codiedev
@@ -237,6 +262,10 @@ codiedev note — capture a passing thought, linked to your session
237
262
  codiedev note "idempotency key is worth a follow-up"
238
263
  codiedev note "timeout threshold feels arbitrary — validate on slow networks"
239
264
  `.trim(),
265
+ ignore: IGNORE_HELP,
266
+ unignore: IGNORE_HELP,
267
+ ignored: IGNORE_HELP,
268
+ capture: IGNORE_HELP,
240
269
  promote: `
241
270
  codiedev promote — promote an auto-extracted artifact to authored
242
271
 
@@ -482,6 +511,15 @@ async function main() {
482
511
  case "react":
483
512
  await (0, react_1.runReact)(rest);
484
513
  return;
514
+ case "ignore":
515
+ case "unignore":
516
+ case "ignored":
517
+ await (0, capture_1.runCaptureCommand)([command, ...rest]);
518
+ return;
519
+ case "capture":
520
+ // Back-compat alias for the earlier `codiedev capture <sub>` form.
521
+ await (0, capture_1.runCaptureCommand)(rest);
522
+ return;
485
523
  default:
486
524
  console.error(`Unknown command: ${command}`);
487
525
  console.error(HELP);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};