@sickr/cli 0.9.7 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -11,8 +11,9 @@ import { buildSharePayload, buildCombinedPayload, publish, PublishError } from '
11
11
  import { AUTH_ENDPOINT, readCredentials, writeCredentials, clearCredentials, startDevice, pollDevice, sleep } from './auth.js';
12
12
  import { ui, card, kv } from './ui.js';
13
13
  import { AGENT_API_URL, clearAgentCredentials, disconnectAgent, fetchAgentStatus, pollAgentConnect, readAgentCredentials, rotateAgentKey, startAgentConnect, writeAgentCredentials, } from './agentAuth.js';
14
+ import { PROVIDERS, recordCommandFor } from './providers.js';
14
15
  const REPLAY_ENDPOINT = process.env.SICKR_REPLAY_ENDPOINT ?? 'https://sickr.ai/api/replay';
15
- const COMMANDS = ['init', 'record', 'open', 'list', 'share', 'stop', 'clear', 'login', 'logout', 'whoami', 'agent', 'live', 'run', 'replay', 'workflow', 'help'];
16
+ const COMMANDS = ['init', 'record', 'open', 'list', 'share', 'stop', 'clear', 'login', 'logout', 'whoami', 'agent', 'live', 'run', 'replay', 'prime', 'workflow', 'start', 'status', 'help'];
16
17
  export function parseCommand(argv) {
17
18
  const c = argv[0];
18
19
  return c && COMMANDS.includes(c) ? c : null;
@@ -26,7 +27,7 @@ export function replaySubcommand(rest) {
26
27
  }
27
28
  return null;
28
29
  }
29
- export function workflowAgentAlias(rest) {
30
+ export function primeAgentAlias(rest) {
30
31
  const sub = rest[0];
31
32
  if (sub === 'connect' || sub === 'rotate' || sub === 'disconnect')
32
33
  return [...rest];
@@ -35,7 +36,7 @@ export function workflowAgentAlias(rest) {
35
36
  function withoutFirst(rest) {
36
37
  return rest.slice(1);
37
38
  }
38
- export function buildWorkflowInvocation(rest, command = 'uvx') {
39
+ export function buildPrimeInvocation(rest, command = 'uvx') {
39
40
  const sub = rest[0] ?? 'status';
40
41
  const tail = withoutFirst(rest);
41
42
  if (sub === 'start') {
@@ -51,80 +52,92 @@ export function buildWorkflowInvocation(rest, command = 'uvx') {
51
52
  }
52
53
  return { command, args: rest };
53
54
  }
54
- export const HELP = `sickr — record, replay, and remote-control your AI coding agents.
55
+ export const workflowAgentAlias = primeAgentAlias;
56
+ export const buildWorkflowInvocation = buildPrimeInvocation;
57
+ export function runtimeConfigSummary(config) {
58
+ const modeLabel = {
59
+ replay: 'Replay',
60
+ live: 'Live',
61
+ run: 'Run',
62
+ prime_workflow: 'Prime Workflow',
63
+ };
64
+ const provider = PROVIDERS[config.provider];
65
+ return `${config.agent_id}: ${modeLabel[config.mode]} on ${provider.displayName}`;
66
+ }
67
+ function providerFromFlags(args) {
68
+ if (args.includes('--codex'))
69
+ return 'codex';
70
+ if (args.includes('--gemini'))
71
+ return 'gemini';
72
+ if (args.includes('--cursor'))
73
+ return 'cursor';
74
+ if (args.includes('--local'))
75
+ return 'local';
76
+ return 'claude';
77
+ }
78
+ export const HELP = `sickr - replay, live view, remote control, and Prime Workflow for AI coding agents.
55
79
 
56
80
  Usage: npx @sickr/cli <command> [options]
57
81
 
58
- REPLAY (free) — local recording of every prompt, edit and command.
82
+ ACCOUNT
83
+ login Sign in with GitHub.
84
+ logout Forget the local login.
85
+ whoami Show who you're signed in as.
86
+
87
+ START HERE
88
+ start --agent-id <id>
89
+ Fetch the configured runtime from workflow.sickr.ai
90
+ and start the right mode for that agent: Prime Workflow,
91
+ Run, Live, or Replay.
92
+
93
+ REPLAY (free) - local recording of every prompt, edit and command.
59
94
  replay Install Claude + Codex recording hooks. Use the agents
60
- as normal a redacted timeline is captured to
95
+ as normal - a redacted timeline is captured to
61
96
  ~/.sickr/runs.
62
- replay open [id] Render the newest run (or a specific id) as a local
63
- HTML timeline. Combine across agents with --today,
64
- --since <2h|30m|1d>, or --all (+ --claude / --codex).
65
- replay share [id] Publish a redacted run to sickr.ai/r/<id> (asks first).
66
- Add --yes to skip the prompt; --open to open after.
67
- Or combine a window: --today / --since <dur> / --all.
68
- Anon links live 24h; signed-in links live 7 days.
69
- replay list List recorded runs, newest first (+ --claude/--codex).
70
- replay stop Remove sickr hooks from this project (keeps runs).
71
- replay clear Delete all local runs in ~/.sickr/runs (asks first).
97
+ replay open [id] Render the newest run as a local HTML timeline.
98
+ replay share [id] Publish a redacted run to sickr.ai/r/<id>.
99
+ replay list List recorded runs, newest first.
100
+ replay stop Remove sickr hooks from this project.
101
+ replay clear Delete all local runs in ~/.sickr/runs.
72
102
 
73
- LIVE LOOK (Replay Pro) passive streaming to a watching browser.
74
- live Stream the current session to sickr.ai/r/<your-link> in
75
- real time. Browser steer messages land in
76
- ~/.sickr/inbox/<urlid>.md and your terminal prints them.
77
- Requires \`sickr login\` + Replay Pro entitlement.
103
+ LIVE ($9) - passive streaming to a watching browser.
104
+ live Stream events to sickr.ai/r/<your-link>. Browser text is
105
+ queued to ~/.sickr/inbox/<urlid>.md; it does not control
106
+ an already-running agent.
78
107
  live status Show pid + connection state.
79
108
  live stop Stop the sidecar.
80
109
 
81
- REMOTE CONTROL (Replay Pro) browser keystrokes drive the agent.
82
- run <agent> Wrap an agent in a PTY sickr owns; browser steer
83
- messages are written directly into the agent's stdin.
84
- Real remote control, not pasteboard.
85
- sickr run claude Claude Code under sickr
86
- sickr run codex Codex under sickr
87
- sickr run <bin> arbitrary binary
88
- Flags:
89
- --mode auto (default) inject the agent's
90
- "no prompt / full perms" flag so it
91
- doesn't stall on tool confirms the
92
- operator can't see.
93
- --mode interactive pass agent flags through
94
- verbatim — agent prompts for tool use.
95
- Requires \`sickr login\` + Replay Pro entitlement.
96
- Codex 0.133+ needs \`/hooks\` typed once to trust the
97
- recorder; Claude is silent (hooks auto-installed).
110
+ RUN ($12) - remote control from the browser.
111
+ run <agent> Wrap an agent in a PTY sickr owns; browser steer messages
112
+ are written directly into the agent's stdin.
113
+ sickr run claude
114
+ sickr run codex
115
+ sickr run gemini
116
+ sickr run cursor-agent
117
+ sickr run <bin>
118
+ Flags: --mode auto (default) | --mode interactive
98
119
 
99
- ACCOUNT
100
- login Sign in with GitHub. Unlocks persistent shares
101
- (24h 7d) and Replay Pro entitlement.
102
- logout Forget the local login.
103
- whoami Show who you're signed in as.
120
+ PRIME WORKFLOW - governed ticket execution.
121
+ prime connect --agent-id <id> Approve this machine for a configured
122
+ Prime Workflow agent.
123
+ prime start --agent-id <id> Start PRIME for an agent.
124
+ prime status Show running daemon status.
125
+ prime stop Stop the local PRIME daemon.
126
+ prime rotate | disconnect Rotate or revoke this machine's key.
104
127
 
105
- WORKFLOW (sickr-managed agent + ticketing)
106
- workflow connect --agent-id <id> Approve this machine for a configured
107
- workflow agent (GitHub browser flow).
108
- workflow start --agent-id <id> Start the orchestrator for an agent.
109
- workflow status Show running daemon status.
110
- workflow rotate | disconnect Rotate or revoke this machine's key.
128
+ Product modes are ordered: Prime Workflow > Run > Live > Replay.
129
+ Prime Workflow runs PRIME for agentic or hybrid teams: plan, review,
130
+ implement, merge, evaluate, and ship under control.
111
131
 
112
- help Show this help.
132
+ Requires Node 20+. Codex capture needs Codex CLI 0.133+ and one manual
133
+ /hooks trust approval. Gemini/Cursor/local runners are adapter-backed and
134
+ may have provider-specific setup prompts.
113
135
 
114
- Requires Node 20+. Codex capture needs Codex CLI 0.133+.
115
-
116
- ────────────────────────────────────────────────────────────────────
117
- sickr also governs your whole team — issue tracking + humans + agents on one
118
- board, gates & approvals, signed audit trail, runs 24/7. Free tier available;
119
- bring your own Claude or Codex subscription. → https://sickr.ai
136
+ https://sickr.ai
120
137
  `;
121
138
  export function currentRunId(cc) {
122
139
  return String(cc.session_id ?? 'session');
123
140
  }
124
- const PROVIDERS = {
125
- claude: { name: 'Claude Code', label: 'Claude', settingsPath: () => join(process.cwd(), '.claude', 'settings.json') },
126
- codex: { name: 'Codex', label: 'Codex', settingsPath: () => join(process.cwd(), '.codex', 'hooks.json') },
127
- };
128
141
  function configPath() {
129
142
  return join(homedir(), '.sickr', 'config.json');
130
143
  }
@@ -158,7 +171,7 @@ function resolveName() {
158
171
  export function handleRecord(input, provider = 'claude') {
159
172
  try {
160
173
  const cc = JSON.parse(input);
161
- appendEvent(currentRunId(cc), cc, { human: resolveName(), agent: PROVIDERS[provider].label });
174
+ appendEvent(currentRunId(cc), cc, { human: resolveName(), agent: PROVIDERS[provider].recordLabel });
162
175
  }
163
176
  catch {
164
177
  /* swallow: recording is best-effort and must not disrupt the session */
@@ -166,9 +179,13 @@ export function handleRecord(input, provider = 'claude') {
166
179
  }
167
180
  export function handleInit(provider, noName = false) {
168
181
  const p = PROVIDERS[provider];
182
+ if (!p.supportsHooks || !p.settingsPath) {
183
+ process.stdout.write(`sickr: ${p.displayName} does not use SICKR hooks. Use \`sickr run ${p.defaultCommand}\` or \`sickr start --agent-id <id>\`.\n`);
184
+ return;
185
+ }
169
186
  const settingsPath = p.settingsPath();
170
187
  const settings = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, 'utf8')) : {};
171
- const command = `npx @sickr/cli record${provider === 'codex' ? ' --codex' : ''}`;
188
+ const command = recordCommandFor(provider);
172
189
  // Remove any prior SICKR hook first, then install the current command — so
173
190
  // re-running init (or a CLI upgrade that changes the command) self-heals
174
191
  // instead of leaving a stale hook. Scoped to this provider's file.
@@ -181,16 +198,16 @@ export function handleInit(provider, noName = false) {
181
198
  const name = noName ? 'Human' : loginName();
182
199
  writeFileSync(configPath(), JSON.stringify({ name }, null, 2) + '\n');
183
200
  const labelLine = `Your prompts will be labelled "${name}"${noName ? '' : ' — run `init --no-name` to anonymize'}.\n`;
184
- const nextSteps = provider === 'codex'
185
- ? 'Next: in Codex, run `/hooks` to review & trust these hooks (Codex gates new hooks),\nthen use Codex as normal and: npx @sickr/cli replay open --codex\n'
186
- : 'Use Claude Code as normal, then: npx @sickr/cli replay open\n';
187
- process.stdout.write(`sickr: installed ${p.name} recording hooks in ${settingsPath}\n` +
201
+ const nextSteps = p.requiresManualHookTrust
202
+ ? `Next: in ${p.displayName}, trust the SICKR recorder when prompted (Codex uses \`/hooks\`),\nthen use the agent as normal and: npx @sickr/cli replay open\n`
203
+ : `Use ${p.displayName} as normal, then: npx @sickr/cli replay open\n`;
204
+ process.stdout.write(`sickr: installed ${p.displayName} recording hooks in ${settingsPath}\n` +
188
205
  `Runs are recorded locally to ${runsDir()} (secrets redacted).\n` +
189
206
  labelLine + nextSteps);
190
207
  }
191
208
  /** Stop recording: remove SICKR's hooks from this project (both providers), keep runs. */
192
209
  export function handleStop() {
193
- const targets = [PROVIDERS.claude.settingsPath(), PROVIDERS.codex.settingsPath()];
210
+ const targets = Object.values(PROVIDERS).filter((p) => p.supportsHooks && p.settingsPath).map((p) => p.settingsPath());
194
211
  const cleaned = [];
195
212
  for (const settingsPath of targets) {
196
213
  if (!existsSync(settingsPath))
@@ -275,9 +292,9 @@ export function latestRunIdFor(agent) {
275
292
  function handleOpen(runId, provider) {
276
293
  let id = runId;
277
294
  if (!id && provider) {
278
- id = latestRunIdFor(PROVIDERS[provider].label) ?? undefined;
295
+ id = latestRunIdFor(PROVIDERS[provider].recordLabel) ?? undefined;
279
296
  if (!id) {
280
- process.stdout.write(`sickr: no ${PROVIDERS[provider].label} runs yet use ${PROVIDERS[provider].name} with the hooks installed, then try again.\n`);
297
+ process.stdout.write(`sickr: no ${PROVIDERS[provider].recordLabel} runs yet - use ${PROVIDERS[provider].displayName} with the hooks installed, then try again.\n`);
281
298
  return;
282
299
  }
283
300
  }
@@ -362,11 +379,11 @@ function handleList(provider) {
362
379
  const dir = runsDir();
363
380
  let files = existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith('.ndjson')) : [];
364
381
  if (provider) {
365
- const want = PROVIDERS[provider].label;
382
+ const want = PROVIDERS[provider].recordLabel;
366
383
  files = files.filter((f) => loadRun(f.replace(/\.ndjson$/, '')).events.some((e) => e.kind === 'response' && e.label === want));
367
384
  }
368
385
  if (files.length === 0) {
369
- process.stdout.write(provider ? `sickr: no ${PROVIDERS[provider].label} runs yet.\n` : 'sickr: no runs yet.\n');
386
+ process.stdout.write(provider ? `sickr: no ${PROVIDERS[provider].recordLabel} runs yet.\n` : 'sickr: no runs yet.\n');
370
387
  return;
371
388
  }
372
389
  files
@@ -430,7 +447,7 @@ function expiryCopy(ttl_days) {
430
447
  return {
431
448
  kind: 'pro',
432
449
  value: `${ttl_days} days`,
433
- tag: 'Replay Pro',
450
+ tag: 'paid retention',
434
451
  footer: [],
435
452
  };
436
453
  if (ttl_days >= 2)
@@ -446,7 +463,7 @@ function expiryCopy(ttl_days) {
446
463
  tag: 'anon link',
447
464
  footer: [
448
465
  'run `npx @sickr/cli login` to extend new links to 7 days.',
449
- 'Replay Pro (live + remote) early access, rolling cohorts:',
466
+ 'Live ($9) and Run ($12) - early access, rolling cohorts:',
450
467
  ' https://sickr.ai/#waitlist',
451
468
  ],
452
469
  };
@@ -463,7 +480,7 @@ function tipLine(text) {
463
480
  }
464
481
  function legacyExpiryLine(ttl_days) {
465
482
  if (ttl_days >= 30)
466
- return `sickr: this link is live for ${ttl_days} days (Replay Pro retention).\n`;
483
+ return `sickr: this link is live for ${ttl_days} days (paid retention).\n`;
467
484
  if (ttl_days >= 2)
468
485
  return `sickr: this link is live for ${ttl_days} days — re-share before it expires to extend.\n`;
469
486
  return `sickr: this link expires in 24h. Run \`npx @sickr/cli login\` to extend to 7 days.\n`;
@@ -511,7 +528,7 @@ async function handleShare(runId, yes, open) {
511
528
  else {
512
529
  process.stdout.write(`sickr: published → ${url}\n` +
513
530
  legacyExpiryLine(ttl_days) +
514
- (exp.kind === 'anon' ? `sickr: Replay Pro (live + remote) early access, rolling out in cohorts https://sickr.ai/#waitlist\n` : ''));
531
+ (exp.kind === 'anon' ? `sickr: Live ($9) and Run ($12) - early access, rolling out in cohorts -> https://sickr.ai/#waitlist\n` : ''));
515
532
  }
516
533
  if (open)
517
534
  openInBrowser(url);
@@ -564,7 +581,7 @@ async function handleShareCombined(sel, yes, open) {
564
581
  else {
565
582
  process.stdout.write(`sickr: published → ${url}\n` +
566
583
  legacyExpiryLine(ttl_days) +
567
- (exp.kind === 'anon' ? `sickr: Replay Pro (live + remote) early access, rolling out in cohorts https://sickr.ai/#waitlist\n` : ''));
584
+ (exp.kind === 'anon' ? `sickr: Live ($9) and Run ($12) - early access, rolling out in cohorts -> https://sickr.ai/#waitlist\n` : ''));
568
585
  }
569
586
  if (open)
570
587
  openInBrowser(url);
@@ -826,15 +843,15 @@ async function fetchReplayProEntitlement() {
826
843
  async function requireReplayPro(commandLabel) {
827
844
  const creds = readCredentials();
828
845
  if (!creds) {
829
- process.stderr.write(`sickr: \`${commandLabel}\` is a Replay Pro feature.\n` +
846
+ process.stderr.write(`sickr: \`${commandLabel}\` requires a paid Live, Run, or Prime Workflow entitlement.\n` +
830
847
  ` run \`sickr login\` first — your Pro entitlement is attached to your GitHub account.\n`);
831
848
  return false;
832
849
  }
833
850
  if (await fetchReplayProEntitlement())
834
851
  return true;
835
- process.stderr.write(`sickr: \`${commandLabel}\` is a Replay Pro feature.\n` +
836
- ` you are signed in as ${creds.login}, but your account isn't on Replay Pro.\n` +
837
- ` join the rolling-cohort waitlist https://sickr.ai/#waitlist\n`);
852
+ process.stderr.write(`sickr: \`${commandLabel}\` requires a paid Live, Run, or Prime Workflow entitlement.\n` +
853
+ ` you are signed in as ${creds.login}, but this account does not have access yet.\n` +
854
+ ` join the rolling-cohort waitlist -> https://sickr.ai/#waitlist\n`);
838
855
  return false;
839
856
  }
840
857
  async function handleReplay(rest) {
@@ -853,11 +870,11 @@ async function handleReplay(rest) {
853
870
  handleInit('codex', noName);
854
871
  return;
855
872
  }
856
- if (agent === 'claude' || agent === 'codex') {
873
+ if (agent === 'claude' || agent === 'codex' || agent === 'gemini' || agent === 'cursor' || agent === 'local') {
857
874
  handleInit(agent, noName);
858
875
  return;
859
876
  }
860
- process.stderr.write('sickr: choose an agent - `sickr replay init claude`, `codex`, or `all`.\n');
877
+ process.stderr.write('sickr: choose an agent - `sickr replay init claude`, `codex`, `gemini`, or `all`.\n');
861
878
  process.exit(1);
862
879
  return;
863
880
  }
@@ -878,12 +895,12 @@ async function handleReplay(rest) {
878
895
  handleOpenCombined(sel);
879
896
  return;
880
897
  }
881
- const openProvider = replayRest.includes('--codex') ? 'codex' : replayRest.includes('--claude') ? 'claude' : undefined;
898
+ const openProvider = replayRest.some((a) => ['--codex', '--claude', '--gemini', '--cursor', '--local'].includes(a)) ? providerFromFlags(replayRest) : undefined;
882
899
  handleOpen(replayRest.find((a) => !a.startsWith('-')), openProvider);
883
900
  return;
884
901
  }
885
902
  if (sub === 'list') {
886
- const listProvider = replayRest.includes('--codex') ? 'codex' : replayRest.includes('--claude') ? 'claude' : undefined;
903
+ const listProvider = replayRest.some((a) => ['--codex', '--claude', '--gemini', '--cursor', '--local'].includes(a)) ? providerFromFlags(replayRest) : undefined;
887
904
  handleList(listProvider);
888
905
  return;
889
906
  }
@@ -924,7 +941,7 @@ async function handleReplay(rest) {
924
941
  return;
925
942
  }
926
943
  process.stdout.write('sickr: replay recording is ready for Claude and Codex.\n' +
927
- ' Replay Pro unlocks live viewing with `sickr replay live`.\n');
944
+ ' Live unlocks browser viewing; Run unlocks browser control.\n');
928
945
  }
929
946
  function commandCandidates(mappedArgs) {
930
947
  const override = process.env.SICKR_ORCHESTRATOR_CMD?.trim();
@@ -938,8 +955,8 @@ function commandCandidates(mappedArgs) {
938
955
  { command: 'python', args: ['-m', 'labudi_orchestrator.cli', ...mappedArgs] },
939
956
  ];
940
957
  }
941
- function runWorkflow(rest) {
942
- const mapped = buildWorkflowInvocation(rest).args;
958
+ function runPrime(rest) {
959
+ const mapped = buildPrimeInvocation(rest).args;
943
960
  const candidates = commandCandidates(mapped);
944
961
  let lastStatus = 1;
945
962
  for (const candidate of candidates) {
@@ -949,18 +966,18 @@ function runWorkflow(rest) {
949
966
  continue;
950
967
  }
951
968
  if (result.error) {
952
- process.stderr.write(`sickr: workflow command failed to start: ${result.error.message}\n`);
969
+ process.stderr.write(`sickr: prime command failed to start: ${result.error.message}\n`);
953
970
  process.exit(1);
954
971
  return;
955
972
  }
956
973
  process.exit(result.status ?? 0);
957
974
  return;
958
975
  }
959
- process.stderr.write('sickr: workflow orchestrator is not available. Install Python package `sickr` with `uv tool install sickr`, or set SICKR_ORCHESTRATOR_CMD.\n');
976
+ process.stderr.write('sickr: Prime Workflow orchestrator is not available. Install Python package `sickr` with `uv tool install sickr`, or set SICKR_ORCHESTRATOR_CMD.\n');
960
977
  process.exit(lastStatus);
961
978
  }
962
- async function handleWorkflow(rest) {
963
- const alias = workflowAgentAlias(rest);
979
+ async function handlePrime(rest) {
980
+ const alias = primeAgentAlias(rest);
964
981
  if (alias) {
965
982
  const sub = alias[0];
966
983
  const agentRest = alias.slice(1);
@@ -977,7 +994,7 @@ async function handleWorkflow(rest) {
977
994
  return;
978
995
  }
979
996
  }
980
- runWorkflow(rest.length ? rest : ['status']);
997
+ runPrime(rest.length ? rest : ['status']);
981
998
  }
982
999
  export async function readStreamWithIdle(input, idleMs = 250, emptyMs = 1500) {
983
1000
  const chunks = [];
@@ -1022,7 +1039,7 @@ async function main() {
1022
1039
  }
1023
1040
  const cmd = parseCommand(argv);
1024
1041
  const rest = argv.slice(1);
1025
- const provider = rest.includes('--codex') ? 'codex' : 'claude';
1042
+ const provider = providerFromFlags(rest);
1026
1043
  switch (cmd) {
1027
1044
  case 'record':
1028
1045
  handleRecord(await readStdin(), provider);
@@ -1035,11 +1052,11 @@ async function main() {
1035
1052
  handleInit('codex', noName);
1036
1053
  return;
1037
1054
  }
1038
- if (agent === 'claude' || agent === 'codex') {
1055
+ if (agent === 'claude' || agent === 'codex' || agent === 'gemini' || agent === 'cursor' || agent === 'local') {
1039
1056
  handleInit(agent, noName);
1040
1057
  return;
1041
1058
  }
1042
- process.stderr.write('sickr: choose an agent `init claude`, `init codex`, or `init all`.\n');
1059
+ process.stderr.write('sickr: choose an agent - `init claude`, `init codex`, `init gemini`, or `init all`.\n');
1043
1060
  process.exit(1);
1044
1061
  return;
1045
1062
  }
@@ -1049,12 +1066,12 @@ async function main() {
1049
1066
  handleOpenCombined(sel);
1050
1067
  return;
1051
1068
  }
1052
- const openProvider = rest.includes('--codex') ? 'codex' : rest.includes('--claude') ? 'claude' : undefined;
1069
+ const openProvider = rest.some((a) => ['--codex', '--claude', '--gemini', '--cursor', '--local'].includes(a)) ? providerFromFlags(rest) : undefined;
1053
1070
  handleOpen(rest.find((a) => !a.startsWith('-')), openProvider);
1054
1071
  return;
1055
1072
  }
1056
1073
  case 'list': {
1057
- const listProvider = rest.includes('--codex') ? 'codex' : rest.includes('--claude') ? 'claude' : undefined;
1074
+ const listProvider = rest.some((a) => ['--codex', '--claude', '--gemini', '--cursor', '--local'].includes(a)) ? providerFromFlags(rest) : undefined;
1058
1075
  handleList(listProvider);
1059
1076
  return;
1060
1077
  }
@@ -1076,8 +1093,17 @@ async function main() {
1076
1093
  case 'replay':
1077
1094
  await handleReplay(rest);
1078
1095
  return;
1096
+ case 'prime':
1097
+ await handlePrime(rest);
1098
+ return;
1079
1099
  case 'workflow':
1080
- await handleWorkflow(rest);
1100
+ await handlePrime(rest);
1101
+ return;
1102
+ case 'start':
1103
+ await handlePrime(['start', ...rest]);
1104
+ return;
1105
+ case 'status':
1106
+ await handlePrime(['status', ...rest]);
1081
1107
  return;
1082
1108
  case 'agent': {
1083
1109
  const sub = rest[0];
package/dist/live.js CHANGED
@@ -136,7 +136,7 @@ export async function startLive(opts = {}) {
136
136
  urlid = await computeUrlid(creds);
137
137
  }
138
138
  catch (e) {
139
- process.stderr.write(`sickr: couldn't resolve live url (${e.message}). Replay Pro required.\n`);
139
+ process.stderr.write(`sickr: couldn't resolve live url (${e.message}). Live entitlement required.\n`);
140
140
  try {
141
141
  unlinkSync(pidPath());
142
142
  }
@@ -0,0 +1,85 @@
1
+ import { join } from 'node:path';
2
+ export const PROVIDERS = {
3
+ claude: {
4
+ provider: 'claude',
5
+ displayName: 'Claude Code',
6
+ recordLabel: 'Claude',
7
+ defaultCommand: 'claude',
8
+ supportsHooks: true,
9
+ requiresManualHookTrust: false,
10
+ supportsPtyControl: true,
11
+ supportsStructuredStream: false,
12
+ settingsPath: () => join(process.cwd(), '.claude', 'settings.json'),
13
+ notes: 'Hooks are installed into .claude/settings.json and Claude reads them without an extra trust command.',
14
+ },
15
+ codex: {
16
+ provider: 'codex',
17
+ displayName: 'Codex',
18
+ recordLabel: 'Codex',
19
+ defaultCommand: 'codex',
20
+ supportsHooks: true,
21
+ requiresManualHookTrust: true,
22
+ supportsPtyControl: true,
23
+ supportsStructuredStream: false,
24
+ settingsPath: () => join(process.cwd(), '.codex', 'hooks.json'),
25
+ recordFlag: '--codex',
26
+ notes: 'Codex requires the user to type /hooks once and trust the SICKR recorder.',
27
+ },
28
+ gemini: {
29
+ provider: 'gemini',
30
+ displayName: 'Gemini CLI',
31
+ recordLabel: 'Gemini',
32
+ defaultCommand: 'gemini',
33
+ supportsHooks: true,
34
+ requiresManualHookTrust: true,
35
+ supportsPtyControl: true,
36
+ supportsStructuredStream: false,
37
+ settingsPath: () => join(process.cwd(), '.gemini', 'settings.json'),
38
+ recordFlag: '--gemini',
39
+ notes: 'Gemini CLI has a hooks surface; SICKR keeps it behind an adapter so trust behavior can be handled per release.',
40
+ },
41
+ cursor: {
42
+ provider: 'cursor',
43
+ displayName: 'Cursor CLI',
44
+ recordLabel: 'Cursor',
45
+ defaultCommand: 'cursor-agent',
46
+ supportsHooks: false,
47
+ requiresManualHookTrust: false,
48
+ supportsPtyControl: true,
49
+ supportsStructuredStream: true,
50
+ notes: 'Cursor is best captured through headless stream-json for workflow jobs; interactive control uses the PTY wrapper.',
51
+ },
52
+ local: {
53
+ provider: 'local',
54
+ displayName: 'Local LLM',
55
+ recordLabel: 'Local',
56
+ defaultCommand: 'sickr-local-agent',
57
+ supportsHooks: false,
58
+ requiresManualHookTrust: false,
59
+ supportsPtyControl: true,
60
+ supportsStructuredStream: true,
61
+ notes: 'Local models should run through a SICKR-owned harness against Ollama, vLLM, llama.cpp, or an OpenAI-compatible endpoint.',
62
+ },
63
+ };
64
+ export function providerForAgent(agent) {
65
+ const a = agent.toLowerCase();
66
+ const base = a.replace(/^.*[\\/]/, '').replace(/\.(cmd|exe|bat|com)$/i, '');
67
+ if (base === 'claude')
68
+ return 'claude';
69
+ if (base === 'codex')
70
+ return 'codex';
71
+ if (base === 'gemini')
72
+ return 'gemini';
73
+ if (base === 'cursor' || base === 'cursor-agent')
74
+ return 'cursor';
75
+ if (base === 'ollama' || base === 'vllm' || base === 'llama.cpp' || base === 'llama-server' || base === 'sickr-local-agent')
76
+ return 'local';
77
+ return null;
78
+ }
79
+ export function recordCommandFor(provider) {
80
+ const p = PROVIDERS[provider];
81
+ return `npx @sickr/cli record${p.recordFlag ? ` ${p.recordFlag}` : ''}`;
82
+ }
83
+ export function modeRank(mode) {
84
+ return { replay: 0, live: 1, run: 2, prime_workflow: 3 }[mode];
85
+ }
package/dist/run.js CHANGED
@@ -29,6 +29,7 @@ import { readCredentials } from './auth.js';
29
29
  import { runsDir } from './recorder.js';
30
30
  import { mergeHooks, removeHooks } from './hookConfig.js';
31
31
  import { LIVE_BASE, decodeWsPayload, splitJsonObjects } from './live.js';
32
+ import { PROVIDERS, providerForAgent, recordCommandFor } from './providers.js';
32
33
  /** Resolve the agent binary path. Precedence:
33
34
  * 1. SICKR_AGENT_BIN_<NAME> env (e.g. SICKR_AGENT_BIN_CLAUDE)
34
35
  * 2. <NAME>_BIN env (e.g. CLAUDE_BIN)
@@ -182,12 +183,13 @@ function tailFrom(path, from) {
182
183
  * hooks were ensured / installed; false if the agent name isn't
183
184
  * one we know how to init for. */
184
185
  export function ensureRecordingHooks(agent) {
185
- const provider = agent === 'claude' || agent === 'codex' ? agent : null;
186
+ const provider = providerForAgent(agent);
186
187
  if (!provider)
187
188
  return false; // Custom binaries — caller decides whether to install hooks
188
- const settingsPath = provider === 'codex'
189
- ? join(process.cwd(), '.codex', 'hooks.json')
190
- : join(process.cwd(), '.claude', 'settings.json');
189
+ const adapter = PROVIDERS[provider];
190
+ if (!adapter.supportsHooks || !adapter.settingsPath)
191
+ return false;
192
+ const settingsPath = adapter.settingsPath();
191
193
  let existing = {};
192
194
  if (existsSync(settingsPath)) {
193
195
  try {
@@ -208,7 +210,7 @@ export function ensureRecordingHooks(agent) {
208
210
  // Dynamic import keeps the file from pulling hookConfig at module
209
211
  // load (tiny boot perf + lets tests run without the module).
210
212
  // eslint-disable-next-line @typescript-eslint/no-require-imports
211
- const command = `npx @sickr/cli record${provider === 'codex' ? ' --codex' : ''}`;
213
+ const command = recordCommandFor(provider);
212
214
  const merged = mergeHooks(removeHooks(existing), command);
213
215
  mkdirSync(join(settingsPath, '..'), { recursive: true });
214
216
  writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + '\n');
@@ -272,7 +274,7 @@ export async function startRun(opts) {
272
274
  urlid = await computeUrlid(creds);
273
275
  }
274
276
  catch (e) {
275
- process.stderr.write(`sickr: couldn't resolve live url (${e.message}). Replay Pro required.\n`);
277
+ process.stderr.write(`sickr: couldn't resolve live url (${e.message}). Run entitlement required.\n`);
276
278
  process.exit(4);
277
279
  }
278
280
  // Ensure recording hooks are installed in the cwd for known agents.
package/package.json CHANGED
@@ -1,18 +1,27 @@
1
1
  {
2
2
  "name": "@sickr/cli",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "type": "module",
5
5
  "description": "npx @sickr/cli - replay, live look, and workflow orchestration for AI coding agents.",
6
- "bin": { "replay": "dist/cli.js", "sickr": "dist/cli.js" },
7
- "files": ["dist"],
8
- "publishConfig": { "access": "public" },
6
+ "bin": {
7
+ "replay": "dist/cli.js",
8
+ "sickr": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
9
16
  "scripts": {
10
17
  "build": "tsc",
11
18
  "test": "vitest run",
12
19
  "dev": "tsc -w",
13
20
  "prepublishOnly": "npm run build && npm test && node scripts/pre-publish-check.mjs"
14
21
  },
15
- "engines": { "node": ">=20" },
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
16
25
  "license": "UNLICENSED",
17
26
  "optionalDependencies": {
18
27
  "node-pty": "^1.0.0",