atris 3.30.0 → 3.30.2

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.
Files changed (69) hide show
  1. package/AGENTS.md +6 -0
  2. package/atris/AGENTS.md +11 -0
  3. package/atris/CLAUDE.md +5 -0
  4. package/atris/atris.md +19 -4
  5. package/atris/policies/atris-design.md +71 -0
  6. package/atris/policies/design-seed.md +187 -0
  7. package/atris/skills/atris/SKILL.md +26 -3
  8. package/atris/skills/design/SKILL.md +3 -2
  9. package/atris/skills/loop/SKILL.md +5 -3
  10. package/atris/team/_template/MEMBER.md +19 -0
  11. package/atris.md +63 -23
  12. package/ax +1434 -1698
  13. package/bin/atris.js +5 -1
  14. package/commands/agent-spawn.js +2 -2
  15. package/commands/brain.js +92 -7
  16. package/commands/brainstorm.js +62 -22
  17. package/commands/business-sync.js +92 -8
  18. package/commands/business.js +13 -7
  19. package/commands/chat-scan.js +102 -0
  20. package/commands/computer.js +758 -15
  21. package/commands/deck.js +505 -105
  22. package/commands/init.js +6 -1
  23. package/commands/launchpad.js +638 -0
  24. package/commands/log-sync.js +44 -13
  25. package/commands/loop-front.js +290 -0
  26. package/commands/member.js +124 -3
  27. package/commands/mission.js +653 -30
  28. package/commands/pull.js +37 -28
  29. package/commands/pulse.js +11 -8
  30. package/commands/run.js +79 -39
  31. package/commands/sync.js +36 -17
  32. package/commands/task.js +1072 -89
  33. package/commands/workflow.js +6 -6
  34. package/commands/xp.js +13 -1
  35. package/commands/youtube.js +221 -7
  36. package/decks/README.md +89 -0
  37. package/decks/archetype-catalog.json +180 -0
  38. package/decks/atris-antislop-pitch.json +48 -0
  39. package/decks/atris-archetypes-v2.json +83 -0
  40. package/decks/atris-one-loop-pitch.json +80 -0
  41. package/decks/atris-seed-pitch-v3.json +118 -0
  42. package/decks/atris-seed-pitch-v4-skeleton.json +106 -0
  43. package/decks/atris-seed-pitch-v5.json +109 -0
  44. package/decks/atris-seed-pitch-v6.json +137 -0
  45. package/decks/atris-seed-pitch-v7.json +133 -0
  46. package/decks/atris-single-shot-proof.json +74 -0
  47. package/decks/mark-pincus-narrative.json +102 -0
  48. package/decks/mark-pincus-sourcery.json +94 -0
  49. package/decks/yash-applied-compute-detailed.json +150 -0
  50. package/decks/yash-applied-compute-generalist.json +82 -0
  51. package/decks/yash-applied-compute-narrative.json +54 -0
  52. package/lib/ax-prefs.js +5 -12
  53. package/lib/chat-log-scan.js +377 -0
  54. package/lib/context-gatherer.js +35 -1
  55. package/lib/deck-compose.js +145 -0
  56. package/lib/deck-history.js +64 -0
  57. package/lib/deck-layout.js +169 -0
  58. package/lib/deck-review.js +431 -0
  59. package/lib/deck-schema.js +154 -0
  60. package/lib/file-ops.js +3 -3
  61. package/lib/functional-owner.js +189 -0
  62. package/lib/journal.js +7 -73
  63. package/lib/slides-deck.js +512 -58
  64. package/lib/task-db.js +128 -11
  65. package/package.json +2 -1
  66. package/templates/business-starter/team/START_HERE.md +12 -8
  67. package/utils/auth.js +4 -0
  68. package/utils/config.js +4 -0
  69. package/atris/atrisDev.md +0 -717
@@ -13,6 +13,7 @@
13
13
  * atris computer ls [path] — List files
14
14
  * atris computer cat <path> — Read a file
15
15
  * atris computer exec <prompt> — Run with LLM (Claude Code)
16
+ * atris computer recruiting — Open the recruiting workflow shortcut
16
17
  */
17
18
 
18
19
  const fs = require('fs');
@@ -42,6 +43,19 @@ function sleep(ms) {
42
43
 
43
44
  const VALID_CLOUD_WORKERS = new Set(['claude', 'openai']);
44
45
  const LOCAL_BRIDGE_RECONNECT_MS = 2000;
46
+ const VALID_COMPUTER_TYPES = new Set([
47
+ 'general',
48
+ 'business_ops',
49
+ 'codeops',
50
+ 'research',
51
+ 'crm',
52
+ 'reporting',
53
+ 'recruiting',
54
+ 'event_ops',
55
+ 'support',
56
+ ]);
57
+ const RECRUITING_BUSINESS_SLUG = 'atris-labs';
58
+ const RECRUITING_LOCAL_SYNC_COMMANDS = new Set(['pull', 'push', 'publish', 'watch', 'review', 'doctor']);
45
59
  const KNOWN_CHAT_COMMANDS = new Set([
46
60
  '/audit',
47
61
  '/exit',
@@ -90,6 +104,21 @@ Never hide failures.
90
104
  A blocked check or missing permission is evidence, not success.
91
105
  `.trim();
92
106
 
107
+ const RECRUITING_WORKFLOW_PROMPT = `
108
+ ## Atris Recruiting Workflow
109
+
110
+ You are running inside the recruiting computer.
111
+ Optimize for recruiter throughput: pipeline clarity, candidate follow-up, role context, interview loops, and decision notes.
112
+ Do not send outreach, DMs, emails, or calendar invites without explicit operator approval.
113
+
114
+ For each work block, report:
115
+ - role or pipeline touched
116
+ - candidates or sources reviewed
117
+ - artifact changed
118
+ - follow-up owner
119
+ - sync/proof status
120
+ `.trim();
121
+
93
122
  function color(code, value) {
94
123
  if (process.env.NO_COLOR || !process.stdout.isTTY) return String(value);
95
124
  return `\x1b[${code}m${value}\x1b[0m`;
@@ -266,7 +295,8 @@ function printCloudStartPanel(ctx, worker, model, billingLabel, authSummary = nu
266
295
 
267
296
  function appendSystemPrompt(basePrompt, extraPrompt) {
268
297
  if (!extraPrompt) return basePrompt || null;
269
- if (basePrompt && basePrompt.includes('## Atris CodeOps Workflow')) return basePrompt;
298
+ const marker = String(extraPrompt).split('\n', 1)[0];
299
+ if (basePrompt && marker && basePrompt.includes(marker)) return basePrompt;
270
300
  if (!basePrompt) return extraPrompt;
271
301
  return `${String(basePrompt).trim()}\n\n${extraPrompt}`;
272
302
  }
@@ -280,6 +310,15 @@ function codeOpsCloudOptions(options = {}) {
280
310
  };
281
311
  }
282
312
 
313
+ function recruitingCloudOptions(options = {}) {
314
+ return {
315
+ ...options,
316
+ worker: options.worker || 'claude',
317
+ mode: 'recruiting',
318
+ systemPrompt: appendSystemPrompt(options.systemPrompt, RECRUITING_WORKFLOW_PROMPT),
319
+ };
320
+ }
321
+
283
322
  function printCodeOpsStartPanel(ctx, worker, model, billingLabel, authSummary = null) {
284
323
  console.log('');
285
324
  console.log(ui.bold('Atris CodeOps Computer'));
@@ -306,6 +345,502 @@ function printCodeOpsWorkflowContract() {
306
345
  console.log(' Full permissions stay on; the workflow contract controls how the computer uses them.');
307
346
  }
308
347
 
348
+ function printRecruitingWorkflowContract() {
349
+ console.log('');
350
+ console.log(ui.bold('Recruiting workflow'));
351
+ console.log(' pipeline -> candidates -> next touch -> owner -> proof -> sync');
352
+ console.log('');
353
+ console.log(' No external outreach, DMs, emails, or calendar invites without explicit operator approval.');
354
+ console.log(' Required final evidence: pipeline touched, candidates reviewed, artifact changed, follow-up owner, sync/proof status.');
355
+ }
356
+
357
+ function printRecruitingComputerHelp() {
358
+ console.log('Usage: atris computer recruiting [chat|status|sync|doctor|pull|push|publish|watch|review|wake|sleep|run|grep|ls|cat|exec|audit|workflow|create]');
359
+ console.log('');
360
+ console.log('Examples:');
361
+ console.log(' atris computer recruiting');
362
+ console.log(' atris computer recruiting status');
363
+ console.log(' atris computer recruiting doctor');
364
+ console.log(' atris computer recruiting sync');
365
+ console.log(' atris computer recruiting pull');
366
+ console.log(' atris computer recruiting push --dry-run');
367
+ console.log(' atris computer recruiting publish --dry-run');
368
+ console.log(' atris computer recruiting watch');
369
+ console.log(' atris computer recruiting run "pwd && find . -maxdepth 2 -type f | head"');
370
+ console.log(" atris computer recruiting exec \"Summarize today's candidate follow-ups\"");
371
+ console.log(' atris computer recruiting create');
372
+ }
373
+
374
+ function displayHomeRelativePath(targetPath) {
375
+ const home = os.homedir();
376
+ if (targetPath && home && targetPath === home) return '~';
377
+ if (targetPath && home && targetPath.startsWith(`${home}${path.sep}`)) {
378
+ return `~${targetPath.slice(home.length)}`;
379
+ }
380
+ return targetPath;
381
+ }
382
+
383
+ function recruitingBusinessWorkspacePath(slug = RECRUITING_BUSINESS_SLUG) {
384
+ const root = process.env.ATRIS_BUSINESS_ROOT || path.join(os.homedir(), 'arena', 'atris-business');
385
+ return path.join(root, slug);
386
+ }
387
+
388
+ function normalizeBusinessSlug(value) {
389
+ return String(value || '')
390
+ .trim()
391
+ .toLowerCase()
392
+ .replace(/[^a-z0-9]+/g, '-')
393
+ .replace(/^-|-$/g, '');
394
+ }
395
+
396
+ function bindingMatchesBusinessSlug(binding, slug) {
397
+ if (!binding) return false;
398
+ const wanted = normalizeBusinessSlug(slug);
399
+ return [binding.slug, binding.business_slug, binding.name]
400
+ .map(normalizeBusinessSlug)
401
+ .some((candidate) => candidate && candidate === wanted);
402
+ }
403
+
404
+ function bindingBusinessLabel(binding) {
405
+ return binding?.slug || binding?.business_slug || binding?.name || 'unknown';
406
+ }
407
+
408
+ function resolveRecruitingSyncWorkspace(slug = RECRUITING_BUSINESS_SLUG) {
409
+ const currentBinding = readBusinessBinding();
410
+ if (bindingMatchesBusinessSlug(currentBinding, slug)) {
411
+ return {
412
+ cwd: process.cwd(),
413
+ binding: currentBinding,
414
+ source: 'current',
415
+ };
416
+ }
417
+
418
+ const canonicalCwd = recruitingBusinessWorkspacePath(slug);
419
+ const canonicalBinding = readBusinessBinding(canonicalCwd);
420
+ if (bindingMatchesBusinessSlug(canonicalBinding, slug)) {
421
+ return {
422
+ cwd: canonicalCwd,
423
+ binding: canonicalBinding,
424
+ source: 'canonical',
425
+ };
426
+ }
427
+
428
+ return null;
429
+ }
430
+
431
+ function printRecruitingSyncNextSteps(slug = RECRUITING_BUSINESS_SLUG, workspacePath = null) {
432
+ const target = workspacePath || recruitingBusinessWorkspacePath(slug);
433
+ console.log('');
434
+ console.log('Recruiting sync commands');
435
+ console.log(` cd ${displayHomeRelativePath(target)}`);
436
+ console.log(' atris computer recruiting doctor');
437
+ console.log(' atris computer recruiting pull');
438
+ console.log(' atris computer recruiting push --dry-run');
439
+ console.log(' atris computer recruiting publish --dry-run');
440
+ console.log(' atris sync --status');
441
+ console.log(' atris sync --dry-run');
442
+ console.log(' atris sync');
443
+ console.log(' atris sync --watch');
444
+ }
445
+
446
+ function recruitingLocalSyncCommand(action, slug = RECRUITING_BUSINESS_SLUG, args = []) {
447
+ const withFlag = (items, flag) => items.includes(flag) ? items : [...items, flag];
448
+ switch (action) {
449
+ case 'pull':
450
+ return ['pull', slug, '--keep-local', '--fail-on-conflict', ...args];
451
+ case 'push':
452
+ return ['push', slug, ...args];
453
+ case 'publish':
454
+ return ['push', slug, ...withFlag(args, '--allow-broad-workspace')];
455
+ case 'watch':
456
+ return ['sync', '--watch', ...args];
457
+ case 'review':
458
+ return ['sync', '--review', ...args];
459
+ case 'doctor':
460
+ return ['sync', '--status', ...args];
461
+ default:
462
+ return ['sync', ...args];
463
+ }
464
+ }
465
+
466
+ function printRecruitingLocalSyncCommandHelp(action, slug = RECRUITING_BUSINESS_SLUG) {
467
+ const command = recruitingLocalSyncCommand(action, slug, (action === 'push' || action === 'publish') ? ['--dry-run'] : []);
468
+ console.log(`Usage: atris computer recruiting ${action} [flags]`);
469
+ console.log('');
470
+ console.log('Runs from the current or canonical Atris Labs recruiting workspace.');
471
+ if (action === 'pull') {
472
+ console.log('Use --dry-run first; use --apply to write into a dirty local workspace.');
473
+ } else if (action === 'push') {
474
+ console.log('Use --dry-run first; after reviewing a broad publish, add --allow-broad-workspace.');
475
+ } else if (action === 'publish') {
476
+ console.log('Reviewed publish shortcut. Use --dry-run first, then run without --dry-run.');
477
+ }
478
+ console.log('');
479
+ console.log('Underlying command:');
480
+ console.log(` atris ${command.join(' ')}`);
481
+ if (action === 'push') {
482
+ console.log('');
483
+ console.log('Reviewed broad publish:');
484
+ console.log(' atris computer recruiting push --dry-run --allow-broad-workspace');
485
+ console.log(' atris computer recruiting push --allow-broad-workspace');
486
+ } else if (action === 'publish') {
487
+ console.log('');
488
+ console.log('Reviewed publish:');
489
+ console.log(' atris computer recruiting publish --dry-run');
490
+ console.log(' atris computer recruiting publish');
491
+ }
492
+ }
493
+
494
+ function withoutRecruitingWrapperFlags(action, args = []) {
495
+ if (action !== 'pull') return args;
496
+ return args.filter((arg) => arg !== '--apply');
497
+ }
498
+
499
+ function gitDirtySummary(cwd) {
500
+ const result = spawnSync('git', ['-C', cwd, 'status', '--short'], {
501
+ encoding: 'utf8',
502
+ env: { ...process.env, ATRIS_SKIP_UPDATE_CHECK: '1' },
503
+ });
504
+ if (result.status !== 0) return null;
505
+ const lines = String(result.stdout || '').split(/\r?\n/).filter(Boolean);
506
+ return {
507
+ count: lines.length,
508
+ sample: lines.slice(0, 5),
509
+ };
510
+ }
511
+
512
+ function gitOutput(cwd, args = []) {
513
+ const result = spawnSync('git', ['-C', cwd, ...args], {
514
+ encoding: 'utf8',
515
+ env: { ...process.env, ATRIS_SKIP_UPDATE_CHECK: '1' },
516
+ });
517
+ if (result.status !== 0) return '';
518
+ return String(result.stdout || '');
519
+ }
520
+
521
+ function recruitingCheckpointStamp() {
522
+ return (process.env.ATRIS_RECRUITING_CHECKPOINT_STAMP || new Date().toISOString())
523
+ .replace(/[-:]/g, '')
524
+ .replace(/\.\d{3}Z$/, 'Z');
525
+ }
526
+
527
+ function listRecruitingUntrackedFiles(cwd) {
528
+ return gitOutput(cwd, ['ls-files', '--others', '--exclude-standard'])
529
+ .split(/\r?\n/)
530
+ .map((line) => line.trim())
531
+ .filter(Boolean)
532
+ .filter((filePath) => !filePath.startsWith('.atris/sync/checkpoints/'));
533
+ }
534
+
535
+ function copyRecruitingUntrackedFiles(cwd, checkpointDir, untrackedFiles = []) {
536
+ let copied = 0;
537
+ const targetRoot = path.join(checkpointDir, 'untracked');
538
+ for (const filePath of untrackedFiles) {
539
+ const source = path.join(cwd, filePath);
540
+ const target = path.join(targetRoot, filePath);
541
+ try {
542
+ if (!fs.statSync(source).isFile()) continue;
543
+ fs.mkdirSync(path.dirname(target), { recursive: true });
544
+ fs.copyFileSync(source, target);
545
+ copied++;
546
+ } catch {
547
+ // Some files move while tools write state; keep the checkpoint best-effort.
548
+ }
549
+ }
550
+ return copied;
551
+ }
552
+
553
+ function writeRecruitingPullCheckpoint(cwd, dirty) {
554
+ if (!dirty || dirty.count === 0) return null;
555
+
556
+ const stamp = recruitingCheckpointStamp();
557
+ const relDir = `.atris/sync/checkpoints/${stamp}`;
558
+ const checkpointDir = path.join(cwd, relDir);
559
+ const untrackedFiles = listRecruitingUntrackedFiles(cwd);
560
+ fs.mkdirSync(checkpointDir, { recursive: true });
561
+
562
+ const trackedPatch = gitOutput(cwd, ['diff', '--binary']);
563
+ const stagedPatch = gitOutput(cwd, ['diff', '--cached', '--binary']);
564
+ fs.writeFileSync(path.join(checkpointDir, 'tracked.patch'), trackedPatch, 'utf8');
565
+ fs.writeFileSync(path.join(checkpointDir, 'staged.patch'), stagedPatch, 'utf8');
566
+ fs.writeFileSync(path.join(checkpointDir, 'untracked.txt'), `${untrackedFiles.join('\n')}${untrackedFiles.length ? '\n' : ''}`, 'utf8');
567
+ const copiedUntracked = copyRecruitingUntrackedFiles(cwd, checkpointDir, untrackedFiles);
568
+
569
+ const summary = [
570
+ '# Recruiting Pull Checkpoint',
571
+ '',
572
+ `created_at: ${new Date().toISOString()}`,
573
+ `workspace: ${cwd}`,
574
+ `git_status_count: ${dirty.count}`,
575
+ `untracked_files_listed: ${untrackedFiles.length}`,
576
+ `untracked_files_copied: ${copiedUntracked}`,
577
+ '',
578
+ 'Sample status:',
579
+ ...dirty.sample.map((line) => `- ${line}`),
580
+ '',
581
+ 'Restore hints:',
582
+ `- git apply ${relDir}/tracked.patch`,
583
+ `- git apply --cached ${relDir}/staged.patch`,
584
+ `- cp -R ${relDir}/untracked/. .`,
585
+ '',
586
+ ].join('\n');
587
+ fs.writeFileSync(path.join(checkpointDir, 'summary.md'), summary, 'utf8');
588
+
589
+ return {
590
+ relDir,
591
+ summary: `${relDir}/summary.md`,
592
+ untrackedFiles: untrackedFiles.length,
593
+ copiedUntracked,
594
+ };
595
+ }
596
+
597
+ function printRecruitingPullPreflight(summary) {
598
+ console.log('');
599
+ console.log('Recruiting pull preflight');
600
+ console.log(` local git changes: ${summary.count}`);
601
+ summary.sample.forEach((line) => console.log(` ${line}`));
602
+ console.log('');
603
+ console.log('Safe next step');
604
+ console.log(' atris computer recruiting pull --dry-run');
605
+ console.log(' atris computer recruiting pull --apply # writes pull changes and conflict review packet');
606
+ }
607
+
608
+ function latestRecruitingConflictPacket(cwd) {
609
+ const conflictsRoot = path.join(cwd, '.atris', 'sync', 'conflicts');
610
+ const summaries = [];
611
+
612
+ function walk(dir) {
613
+ let entries;
614
+ try {
615
+ entries = fs.readdirSync(dir, { withFileTypes: true });
616
+ } catch {
617
+ return;
618
+ }
619
+ for (const entry of entries) {
620
+ const full = path.join(dir, entry.name);
621
+ if (entry.isDirectory()) walk(full);
622
+ else if (entry.isFile() && entry.name === 'summary.md') {
623
+ summaries.push(path.relative(cwd, full).replace(/\\/g, '/'));
624
+ }
625
+ }
626
+ }
627
+
628
+ walk(conflictsRoot);
629
+ summaries.sort();
630
+ return summaries[summaries.length - 1] || null;
631
+ }
632
+
633
+ function latestRecruitingCheckpoint(cwd) {
634
+ const checkpointRoot = path.join(cwd, '.atris', 'sync', 'checkpoints');
635
+ const summaries = [];
636
+
637
+ function walk(dir) {
638
+ let entries;
639
+ try {
640
+ entries = fs.readdirSync(dir, { withFileTypes: true });
641
+ } catch {
642
+ return;
643
+ }
644
+ for (const entry of entries) {
645
+ const full = path.join(dir, entry.name);
646
+ if (entry.isDirectory()) walk(full);
647
+ else if (entry.isFile() && entry.name === 'summary.md') {
648
+ summaries.push(path.relative(cwd, full).replace(/\\/g, '/'));
649
+ }
650
+ }
651
+ }
652
+
653
+ walk(checkpointRoot);
654
+ summaries.sort();
655
+ return summaries[summaries.length - 1] || null;
656
+ }
657
+
658
+ function printRecruitingSyncDoctor(workspace, slug = RECRUITING_BUSINESS_SLUG) {
659
+ const dirty = gitDirtySummary(workspace.cwd);
660
+ const latestPacket = latestRecruitingConflictPacket(workspace.cwd);
661
+ const latestCheckpoint = latestRecruitingCheckpoint(workspace.cwd);
662
+ console.log('Recruiting sync doctor');
663
+ console.log(` workspace: ${displayHomeRelativePath(workspace.cwd)}`);
664
+ console.log(` business: ${bindingBusinessLabel(workspace.binding) || slug}`);
665
+ if (dirty) {
666
+ console.log(` local git changes: ${dirty.count}`);
667
+ dirty.sample.forEach((line) => console.log(` ${line}`));
668
+ } else {
669
+ console.log(' local git changes: unknown');
670
+ }
671
+ console.log(` review packet: ${latestPacket || 'none'}`);
672
+ console.log(` latest checkpoint: ${latestCheckpoint || 'none'}`);
673
+ console.log('');
674
+ console.log('Next command');
675
+ if (latestPacket) {
676
+ console.log(' atris computer recruiting review');
677
+ } else if (dirty && dirty.count > 0) {
678
+ console.log(' atris computer recruiting pull --dry-run');
679
+ } else {
680
+ console.log(' atris computer recruiting pull --dry-run');
681
+ }
682
+ }
683
+
684
+ function runAtrisCliCommand(cliArgs, cwd) {
685
+ const result = spawnSync(process.execPath, [path.join(__dirname, '..', 'bin', 'atris.js'), ...cliArgs], {
686
+ cwd,
687
+ stdio: 'inherit',
688
+ env: { ...process.env, ATRIS_SKIP_UPDATE_CHECK: '1' },
689
+ });
690
+ if (result.error) throw result.error;
691
+ if (result.signal) {
692
+ process.exitCode = 1;
693
+ return 1;
694
+ }
695
+ process.exitCode = result.status || 0;
696
+ return process.exitCode;
697
+ }
698
+
699
+ function printRecruitingLocalSyncOutcome(action, status = 0, args = []) {
700
+ const isPublishLike = action === 'push' || action === 'publish';
701
+
702
+ if (action === 'pull') {
703
+ console.log('');
704
+ console.log('Recruiting next step');
705
+ if (args.includes('--dry-run')) {
706
+ console.log(' atris computer recruiting pull --apply # writes review packet if conflicts were reported');
707
+ console.log(' atris computer recruiting review');
708
+ console.log(' atris computer recruiting push --dry-run');
709
+ return;
710
+ }
711
+ console.log(' atris computer recruiting review # if conflicts were reported');
712
+ console.log(' atris computer recruiting push --dry-run');
713
+ return;
714
+ }
715
+
716
+ if (isPublishLike && status !== 0) {
717
+ console.log('');
718
+ console.log('Recruiting next step');
719
+ console.log(' If the output says review before publish:');
720
+ console.log(' atris computer recruiting publish --dry-run');
721
+ console.log(' atris computer recruiting publish');
722
+ console.log(' Otherwise:');
723
+ console.log(' atris computer recruiting pull --dry-run');
724
+ console.log(' atris computer recruiting review # if conflicts were reported');
725
+ return;
726
+ }
727
+
728
+ if (action === 'publish' && args.includes('--dry-run')) {
729
+ console.log('');
730
+ console.log('Recruiting next step');
731
+ console.log(' atris computer recruiting publish');
732
+ return;
733
+ }
734
+
735
+ if (action === 'push' && args.includes('--dry-run')) {
736
+ console.log('');
737
+ console.log('Recruiting next step');
738
+ if (args.includes('--allow-broad-workspace')) {
739
+ console.log(' atris computer recruiting push --allow-broad-workspace');
740
+ return;
741
+ }
742
+ console.log(' If the dry-run showed review before publish:');
743
+ console.log(' atris computer recruiting push --dry-run --allow-broad-workspace');
744
+ console.log(' atris computer recruiting push --allow-broad-workspace');
745
+ console.log(' Otherwise:');
746
+ console.log(' atris computer recruiting push');
747
+ }
748
+
749
+ if (action === 'review') {
750
+ console.log('');
751
+ console.log('Review note');
752
+ console.log(' If pull --dry-run reported conflicts but no packet appears:');
753
+ console.log(' atris computer recruiting pull --apply');
754
+ }
755
+ }
756
+
757
+ async function runRecruitingLocalSyncCommand(action, args = [], cloudOptions = {}) {
758
+ const slug = cloudOptions.businessSlug || RECRUITING_BUSINESS_SLUG;
759
+ if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
760
+ printRecruitingLocalSyncCommandHelp(action, slug);
761
+ return;
762
+ }
763
+
764
+ const workspace = resolveRecruitingSyncWorkspace(slug);
765
+ console.log(`Recruiting local ${action}`);
766
+ if (!workspace) {
767
+ const currentBinding = readBusinessBinding();
768
+ if (currentBinding && !bindingMatchesBusinessSlug(currentBinding, slug)) {
769
+ console.log(` current workspace: ${bindingBusinessLabel(currentBinding)} (not ${slug})`);
770
+ } else {
771
+ console.log(' local workspace: not detected in this folder');
772
+ }
773
+ printRecruitingLocalSyncCommandHelp(action, slug);
774
+ printRecruitingSyncNextSteps(slug);
775
+ return;
776
+ }
777
+
778
+ if (workspace.source === 'canonical') {
779
+ console.log(` folder: ${displayHomeRelativePath(workspace.cwd)} (auto-detected)`);
780
+ }
781
+
782
+ if (action === 'doctor') {
783
+ printRecruitingSyncDoctor(workspace, slug);
784
+ return;
785
+ }
786
+
787
+ const commandArgs = withoutRecruitingWrapperFlags(action, args);
788
+ if (action === 'pull' && !args.includes('--dry-run') && !args.includes('--apply')) {
789
+ const dirty = gitDirtySummary(workspace.cwd);
790
+ if (dirty && dirty.count > 0) {
791
+ printRecruitingPullPreflight(dirty);
792
+ process.exitCode = 1;
793
+ return;
794
+ }
795
+ }
796
+ if (action === 'pull' && args.includes('--apply')) {
797
+ const dirty = gitDirtySummary(workspace.cwd);
798
+ const checkpoint = writeRecruitingPullCheckpoint(workspace.cwd, dirty);
799
+ if (checkpoint) {
800
+ console.log('');
801
+ console.log('Recruiting checkpoint');
802
+ console.log(` wrote: ${checkpoint.summary}`);
803
+ console.log(` untracked copied: ${checkpoint.copiedUntracked}/${checkpoint.untrackedFiles}`);
804
+ }
805
+ }
806
+
807
+ const command = recruitingLocalSyncCommand(action, workspace.binding.slug || slug, commandArgs);
808
+ const status = runAtrisCliCommand(command, workspace.cwd);
809
+ printRecruitingLocalSyncOutcome(action, status, commandArgs);
810
+ }
811
+
812
+ async function runRecruitingSyncHelper(args = [], cloudOptions = {}) {
813
+ const slug = cloudOptions.businessSlug || RECRUITING_BUSINESS_SLUG;
814
+ if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
815
+ console.log('Usage: atris computer recruiting sync [--status|--dry-run|--watch|--review|--resolve local|cloud|merge]');
816
+ console.log('');
817
+ console.log('Runs local recruiting workspace sync from the current or canonical Atris Labs folder.');
818
+ printRecruitingSyncNextSteps(slug);
819
+ return;
820
+ }
821
+
822
+ const workspace = resolveRecruitingSyncWorkspace(slug);
823
+ console.log('Recruiting local sync');
824
+ if (!workspace) {
825
+ const currentBinding = readBusinessBinding();
826
+ if (currentBinding && !bindingMatchesBusinessSlug(currentBinding, slug)) {
827
+ console.log(` current workspace: ${bindingBusinessLabel(currentBinding)} (not ${slug})`);
828
+ } else {
829
+ console.log(' local workspace: not detected in this folder');
830
+ }
831
+ printRecruitingSyncNextSteps(slug);
832
+ return;
833
+ }
834
+
835
+ if (workspace.source === 'canonical') {
836
+ console.log(` folder: ${displayHomeRelativePath(workspace.cwd)} (auto-detected)`);
837
+ }
838
+
839
+ const { businessSync } = require('./business-sync');
840
+ await businessSync(args.length > 0 ? args : ['--status'], workspace.cwd);
841
+ printRecruitingSyncNextSteps(workspace.binding.slug || slug, workspace.cwd);
842
+ }
843
+
309
844
  function buildLocalBridgeSystemPrompt(sessionId, localRoot, allowBash) {
310
845
  const endpoint = `/api/cli/sessions/${sessionId}/file-op`;
311
846
  const bashLine = allowBash
@@ -589,6 +1124,7 @@ function parseComputerCreateArgs(argv = []) {
589
1124
  let businessSlug = null;
590
1125
  let help = false;
591
1126
  let setDefault = false;
1127
+ let computerType = null;
592
1128
 
593
1129
  for (let i = 0; i < argv.length; i++) {
594
1130
  const arg = argv[i];
@@ -605,6 +1141,15 @@ function parseComputerCreateArgs(argv = []) {
605
1141
  businessSlug = arg.split('=', 2)[1] || null;
606
1142
  continue;
607
1143
  }
1144
+ if ((arg === '--type' || arg === '-t') && argv[i + 1] && !argv[i + 1].startsWith('-')) {
1145
+ computerType = argv[i + 1];
1146
+ i++;
1147
+ continue;
1148
+ }
1149
+ if (arg.startsWith('--type=')) {
1150
+ computerType = arg.split('=', 2)[1] || null;
1151
+ continue;
1152
+ }
608
1153
  if (arg === '--set-default') {
609
1154
  setDefault = true;
610
1155
  continue;
@@ -615,11 +1160,41 @@ function parseComputerCreateArgs(argv = []) {
615
1160
  return {
616
1161
  name: nameParts.join(' ').trim(),
617
1162
  businessSlug: businessSlug ? String(businessSlug).trim() : null,
1163
+ computerType: computerType ? normalizeComputerType(computerType) : null,
618
1164
  help,
619
1165
  setDefault,
620
1166
  };
621
1167
  }
622
1168
 
1169
+ function computerCreateArgsHaveName(argv = []) {
1170
+ const flagsWithValues = new Set(['--business', '-b', '--type', '-t']);
1171
+ for (let i = 0; i < argv.length; i++) {
1172
+ const arg = argv[i];
1173
+ if (flagsWithValues.has(arg)) {
1174
+ i++;
1175
+ continue;
1176
+ }
1177
+ if (!arg || arg.startsWith('-') || arg === 'help') continue;
1178
+ return true;
1179
+ }
1180
+ return false;
1181
+ }
1182
+
1183
+ function normalizeComputerType(value) {
1184
+ const normalized = String(value || '')
1185
+ .trim()
1186
+ .toLowerCase()
1187
+ .replace(/[\s-]+/g, '_')
1188
+ .replace(/^_+|_+$/g, '');
1189
+ if (normalized === 'business') return 'business_ops';
1190
+ if (normalized === 'event') return 'event_ops';
1191
+ return normalized || 'general';
1192
+ }
1193
+
1194
+ function formatComputerTypeList() {
1195
+ return [...VALID_COMPUTER_TYPES].join(', ');
1196
+ }
1197
+
623
1198
  function parseComputerDeleteArgs(argv = []) {
624
1199
  const options = { help: false, confirm: null };
625
1200
 
@@ -664,6 +1239,7 @@ function findAtrisCodeTerminal() {
664
1239
  envPath,
665
1240
  path.join(__dirname, '..', 'cli', 'atris_code.py'),
666
1241
  path.join(process.cwd(), 'cli', 'atris_code.py'),
1242
+ path.join(os.homedir(), 'arena', 'atrisos-backend', 'cli', 'atris_code.py'),
667
1243
  ].filter(Boolean);
668
1244
 
669
1245
  let dir = process.cwd();
@@ -1077,6 +1653,41 @@ async function resolveComputerCommandContext(token, options = {}) {
1077
1653
  return resolveBusinessContext(token);
1078
1654
  }
1079
1655
 
1656
+ async function resolveTypedBusinessComputerContext(token, options = {}, defaults = {}) {
1657
+ const businessSlug = options.businessSlug || defaults.businessSlug;
1658
+ const computerType = normalizeComputerType(defaults.computerType);
1659
+ if (options.workspaceId) {
1660
+ return resolveComputerCommandContext(token, { ...options, businessSlug });
1661
+ }
1662
+
1663
+ const ctx = await resolveBusinessContextBySlug(token, businessSlug, { preferCache: true });
1664
+ if (!ctx?.businessId) return null;
1665
+ const workspaces = await listBusinessWorkspaces(token, ctx);
1666
+ const workspace = resolveWorkspaceByComputerType(workspaces, computerType);
1667
+ if (!workspace?.id) {
1668
+ return {
1669
+ ...ctx,
1670
+ workspaceId: null,
1671
+ missingComputerType: computerType,
1672
+ };
1673
+ }
1674
+ return {
1675
+ ...ctx,
1676
+ workspaceId: workspace.id,
1677
+ workspaceName: workspace.name || null,
1678
+ computerType,
1679
+ };
1680
+ }
1681
+
1682
+ function printMissingTypedComputer(ctx, computerType, options = {}) {
1683
+ const label = options.label || computerType;
1684
+ const businessSlug = options.businessSlug || ctx?.slug || RECRUITING_BUSINESS_SLUG;
1685
+ console.error(`No ${label} computer found for ${ctx?.businessName || businessSlug}.`);
1686
+ console.error(`Create it: atris computer ${label} create`);
1687
+ console.error(`Or explicitly create one: atris computer create "${label[0].toUpperCase()}${label.slice(1)} Computer" --business ${businessSlug} --type ${computerType}`);
1688
+ process.exitCode = 1;
1689
+ }
1690
+
1080
1691
  function looksLikeWorkspaceId(input) {
1081
1692
  const value = String(input || '').trim();
1082
1693
  if (!value) return false;
@@ -1176,6 +1787,7 @@ function rememberCreatedComputer(ctx, workspace, endpoint = null, options = {})
1176
1787
  name: ctx.businessName,
1177
1788
  slug,
1178
1789
  computer_name: shouldSetDefault ? workspace.name : existing.computer_name,
1790
+ computer_type: shouldSetDefault ? (options.computerType || existing.computer_type) : existing.computer_type,
1179
1791
  endpoint: shouldSetDefault ? (endpoint || existing.endpoint) : existing.endpoint,
1180
1792
  added_at: existing.added_at || new Date().toISOString(),
1181
1793
  updated_at: new Date().toISOString(),
@@ -1231,6 +1843,23 @@ function resolveWorkspaceFromList(workspaces, input) {
1231
1843
  return (workspaces || []).find((workspace) => workspaceMatchesInput(workspace, input)) || null;
1232
1844
  }
1233
1845
 
1846
+ function workspaceComputerType(workspace) {
1847
+ return normalizeComputerType(workspace?.type || workspace?.computer_type || workspace?.workspace_type || '');
1848
+ }
1849
+
1850
+ function workspaceMatchesComputerType(workspace, type) {
1851
+ if (!workspace || !type) return false;
1852
+ const wanted = normalizeComputerType(type);
1853
+ if (workspaceComputerType(workspace) === wanted) return true;
1854
+ const compactWanted = wanted.replace(/_/g, '');
1855
+ const name = String(workspace.name || '').toLowerCase().replace(/[^a-z0-9]+/g, '');
1856
+ return Boolean(compactWanted && name.includes(compactWanted));
1857
+ }
1858
+
1859
+ function resolveWorkspaceByComputerType(workspaces, type) {
1860
+ return (workspaces || []).find((workspace) => workspaceMatchesComputerType(workspace, type)) || null;
1861
+ }
1862
+
1234
1863
  function formatLeaseAge(seconds) {
1235
1864
  const value = Number(seconds);
1236
1865
  if (!Number.isFinite(value) || value < 0) return '-';
@@ -1595,18 +2224,30 @@ async function computerCreate(token, args = [], defaults = {}) {
1595
2224
  if (!options.businessSlug && defaults.businessSlug) {
1596
2225
  options.businessSlug = defaults.businessSlug;
1597
2226
  }
2227
+ if (!options.computerType && defaults.computerType) {
2228
+ options.computerType = normalizeComputerType(defaults.computerType);
2229
+ }
2230
+ const computerType = options.computerType || 'general';
1598
2231
  if (options.help || !options.name) {
1599
- console.log('Usage: atris computer create <name> --business <slug> [--set-default]');
2232
+ console.log('Usage: atris computer create <name> --business <slug> [--type <type>] [--set-default]');
1600
2233
  console.log('');
1601
2234
  console.log('Create a business computer, activate it, and wake it in one command.');
2235
+ console.log(`Types: ${formatComputerTypeList()}`);
1602
2236
  console.log('');
1603
2237
  console.log('Examples:');
1604
- console.log(' atris computer create "My Business Computer" --business <business>');
1605
- console.log(' atris computer create "Support Computer"');
2238
+ console.log(' atris computer create "My Business Computer" --business atris-labs');
2239
+ console.log(' atris computer create "Recruiting Computer" --business atris-labs --type recruiting');
1606
2240
  if (!options.name && !options.help) process.exitCode = 1;
1607
2241
  return;
1608
2242
  }
1609
2243
 
2244
+ if (!VALID_COMPUTER_TYPES.has(computerType)) {
2245
+ console.error(`Invalid computer type: ${computerType}`);
2246
+ console.error(`Expected one of: ${formatComputerTypeList()}`);
2247
+ process.exitCode = 1;
2248
+ return;
2249
+ }
2250
+
1610
2251
  const ctx = await resolveBusinessOwnerForCreate(token, options.businessSlug);
1611
2252
  if (!ctx?.businessId) {
1612
2253
  console.error('No business found.');
@@ -1619,7 +2260,7 @@ async function computerCreate(token, args = [], defaults = {}) {
1619
2260
  const created = await apiRequestJson(`/business/${ctx.businessId}/workspaces`, {
1620
2261
  method: 'POST',
1621
2262
  token,
1622
- body: { name: options.name, type: 'general' },
2263
+ body: { name: options.name, type: computerType },
1623
2264
  });
1624
2265
  if (!created.ok) {
1625
2266
  console.error(`Failed to create workspace: ${created.errorMessage || created.error || created.status}`);
@@ -1663,6 +2304,7 @@ async function computerCreate(token, args = [], defaults = {}) {
1663
2304
  : (wake.data?.status || (activate.ok ? 'activated' : 'warming_up'));
1664
2305
  rememberCreatedComputer(ctx, { ...workspace, id: workspaceId, name: workspace.name || options.name }, endpoint, {
1665
2306
  setDefault: options.setDefault,
2307
+ computerType,
1666
2308
  });
1667
2309
  await bootstrapBusinessComputerRuntime(token, { ...ctx, workspaceId }, 'computer-create');
1668
2310
 
@@ -1670,6 +2312,7 @@ async function computerCreate(token, args = [], defaults = {}) {
1670
2312
  console.log('');
1671
2313
  console.log(`Computer created: ${workspaceId}`);
1672
2314
  console.log(` Name: ${workspace.name || options.name}`);
2315
+ console.log(` Type: ${computerType}`);
1673
2316
  console.log(` Business: ${ctx.businessName}`);
1674
2317
  console.log(` Status: ${status}`);
1675
2318
  if (endpoint) console.log(` Endpoint: ${endpoint}`);
@@ -1810,8 +2453,8 @@ async function computerDelete(token, ctx, options = {}, args = []) {
1810
2453
  console.log('Sleeps the computer first, then deletes the non-default workspace after confirmation.');
1811
2454
  console.log('');
1812
2455
  console.log('Examples:');
1813
- console.log(' atris computer delete --business <business> --workspace ws_123');
1814
- console.log(' atris computer delete --business <business> --workspace ws_123 --confirm "delete ws_123"');
2456
+ console.log(' atris computer delete --business atris-labs --workspace ws_123');
2457
+ console.log(' atris computer delete --business atris-labs --workspace ws_123 --confirm "delete ws_123"');
1815
2458
  return;
1816
2459
  }
1817
2460
 
@@ -2614,9 +3257,12 @@ async function computerChat(token, ctx, initialOptions = {}) {
2614
3257
  }
2615
3258
 
2616
3259
  const isCodeOps = initialOptions.mode === 'codeops' || ctx.slug === 'atris-codeops';
3260
+ const isRecruiting = initialOptions.mode === 'recruiting';
2617
3261
  const oneShotMessage = initialOptions.message != null;
2618
3262
  const chatSystemPrompt = isCodeOps
2619
3263
  ? appendSystemPrompt(initialOptions.systemPrompt, CODEOPS_WORKFLOW_PROMPT)
3264
+ : isRecruiting
3265
+ ? appendSystemPrompt(initialOptions.systemPrompt, RECRUITING_WORKFLOW_PROMPT)
2620
3266
  : initialOptions.systemPrompt;
2621
3267
  let sessionId = `biz-${ctx.businessId.slice(0, 8)}-${Date.now().toString(36)}`;
2622
3268
  const pipedInput = initialOptions.message != null ? null : await readPipedStdin();
@@ -2641,6 +3287,9 @@ async function computerChat(token, ctx, initialOptions = {}) {
2641
3287
  if (!oneShotMessage) {
2642
3288
  if (isCodeOps) {
2643
3289
  printCodeOpsStartPanel(ctx, worker, model, billingLabel, authSummary);
3290
+ } else if (isRecruiting) {
3291
+ printCloudStartPanel(ctx, worker, model, billingLabel, authSummary);
3292
+ printRecruitingWorkflowContract();
2644
3293
  } else {
2645
3294
  printCloudStartPanel(ctx, worker, model, billingLabel, authSummary);
2646
3295
  }
@@ -2687,7 +3336,7 @@ async function computerChat(token, ctx, initialOptions = {}) {
2687
3336
  const rl = readline.createInterface({
2688
3337
  input: process.stdin,
2689
3338
  output: process.stdout,
2690
- prompt: isCodeOps ? 'codeops> ' : 'cloud> ',
3339
+ prompt: isCodeOps ? 'codeops> ' : (isRecruiting ? 'recruiting> ' : 'cloud> '),
2691
3340
  });
2692
3341
 
2693
3342
  rl.prompt();
@@ -2708,6 +3357,9 @@ async function computerChat(token, ctx, initialOptions = {}) {
2708
3357
  authSummary = activeWorker(worker) === 'claude' ? await describeClaudeAuth(token, ctx) : null;
2709
3358
  if (isCodeOps) {
2710
3359
  printCodeOpsStartPanel(ctx, worker, model, billingLabel, authSummary);
3360
+ } else if (isRecruiting) {
3361
+ printCloudStartPanel(ctx, worker, model, billingLabel, authSummary);
3362
+ printRecruitingWorkflowContract();
2711
3363
  } else {
2712
3364
  printCloudStartPanel(ctx, worker, model, billingLabel, authSummary);
2713
3365
  }
@@ -2725,7 +3377,8 @@ async function computerChat(token, ctx, initialOptions = {}) {
2725
3377
  continue;
2726
3378
  }
2727
3379
  if (line === '/workflow') {
2728
- printCodeOpsWorkflowContract();
3380
+ if (isRecruiting) printRecruitingWorkflowContract();
3381
+ else printCodeOpsWorkflowContract();
2729
3382
  rl.prompt();
2730
3383
  continue;
2731
3384
  }
@@ -3203,6 +3856,79 @@ async function computerProof(token, ctx, initialOptions = {}) {
3203
3856
  }
3204
3857
  }
3205
3858
 
3859
+ async function runRecruitingComputerShortcut(token, args, cloudOptions = {}) {
3860
+ const recruitingOptions = recruitingCloudOptions(cloudOptions);
3861
+ const sub = args[1];
3862
+ const rest = args.slice(2).join(' ');
3863
+
3864
+ if (sub === '--help' || sub === 'help') {
3865
+ printRecruitingComputerHelp();
3866
+ return;
3867
+ }
3868
+
3869
+ if (sub === 'create') {
3870
+ const createArgs = args.slice(2);
3871
+ const finalArgs = computerCreateArgsHaveName(createArgs)
3872
+ ? createArgs
3873
+ : ['Recruiting Computer', ...createArgs];
3874
+ return computerCreate(token, finalArgs, {
3875
+ businessSlug: cloudOptions.businessSlug || RECRUITING_BUSINESS_SLUG,
3876
+ computerType: 'recruiting',
3877
+ });
3878
+ }
3879
+
3880
+ if (sub === 'sync') {
3881
+ return runRecruitingSyncHelper(args.slice(2), recruitingOptions);
3882
+ }
3883
+
3884
+ if (RECRUITING_LOCAL_SYNC_COMMANDS.has(sub)) {
3885
+ return runRecruitingLocalSyncCommand(sub, args.slice(2), recruitingOptions);
3886
+ }
3887
+
3888
+ const ctx = await resolveTypedBusinessComputerContext(token, recruitingOptions, {
3889
+ businessSlug: RECRUITING_BUSINESS_SLUG,
3890
+ computerType: 'recruiting',
3891
+ });
3892
+ if (!ctx?.businessId) {
3893
+ console.error('Atris Recruiting is not available for this account.');
3894
+ console.error('Ask an Atris Labs admin to add you, or pass --business <slug>.');
3895
+ process.exitCode = 1;
3896
+ return;
3897
+ }
3898
+ if (!ctx.workspaceId) {
3899
+ printMissingTypedComputer(ctx, 'recruiting', {
3900
+ label: 'recruiting',
3901
+ businessSlug: recruitingOptions.businessSlug || RECRUITING_BUSINESS_SLUG,
3902
+ });
3903
+ return;
3904
+ }
3905
+
3906
+ if (!sub || sub === 'chat') return computerChat(token, ctx, recruitingOptions);
3907
+
3908
+ switch (sub) {
3909
+ case 'status': return computerStatus(token, ctx);
3910
+ case 'up':
3911
+ case 'wake': return computerWake(token, ctx);
3912
+ case 'sleep': return computerSleep(token, ctx);
3913
+ case 'run': return computerRun(token, rest, ctx);
3914
+ case 'grep': return computerGrep(token, rest, ctx);
3915
+ case 'ls': return computerLs(token, rest || undefined, ctx);
3916
+ case 'cat': return computerCat(token, rest, ctx);
3917
+ case 'exec': return computerExec(token, rest, ctx, recruitingOptions);
3918
+ case 'audit': {
3919
+ const limit = rest ? Number.parseInt(rest, 10) : 10;
3920
+ return computerAudit(token, ctx, Number.isFinite(limit) ? limit : 10);
3921
+ }
3922
+ case 'workflow':
3923
+ printRecruitingWorkflowContract();
3924
+ return;
3925
+ default:
3926
+ console.error(`Unknown recruiting subcommand: ${sub}`);
3927
+ console.log('Run: atris computer recruiting help');
3928
+ process.exitCode = 1;
3929
+ }
3930
+ }
3931
+
3206
3932
  async function runComputer() {
3207
3933
  const parsed = parseComputerOptions(process.argv.slice(3));
3208
3934
  const args = parsed.positional;
@@ -3282,6 +4008,19 @@ async function runComputer() {
3282
4008
  }
3283
4009
  }
3284
4010
 
4011
+ if (sub === 'recruiting' && (args[1] === '--help' || args[1] === 'help')) {
4012
+ printRecruitingComputerHelp();
4013
+ return;
4014
+ }
4015
+
4016
+ if (sub === 'recruiting' && args[1] === 'sync') {
4017
+ return runRecruitingSyncHelper(args.slice(2), cloudOptions);
4018
+ }
4019
+
4020
+ if (sub === 'recruiting' && RECRUITING_LOCAL_SYNC_COMMANDS.has(args[1])) {
4021
+ return runRecruitingLocalSyncCommand(args[1], args.slice(2), cloudOptions);
4022
+ }
4023
+
3285
4024
  if (sub === '--help') {
3286
4025
  console.log('Usage: atris computer [mode|command]');
3287
4026
  console.log('');
@@ -3291,7 +4030,7 @@ async function runComputer() {
3291
4030
  console.log(' Owner has many Computers');
3292
4031
  console.log(' Computer = workspace + files + tools + secrets + memory + agents + validation');
3293
4032
  console.log('');
3294
- console.log('Common types: codeops, research, CRM, reporting, event ops, support, business ops.');
4033
+ console.log('Common types: codeops, research, CRM, reporting, recruiting, event ops, support, business ops.');
3295
4034
  console.log('A business can be a company, lab, collective, community, artist, team, or project.');
3296
4035
  console.log('');
3297
4036
  console.log('First use:');
@@ -3316,7 +4055,7 @@ async function runComputer() {
3316
4055
  console.log(' claude|codex Legacy local console backends');
3317
4056
  console.log('');
3318
4057
  console.log('Cloud commands:');
3319
- console.log(' create <name> Create and wake an extra business computer');
4058
+ console.log(' create <name> Create and wake an extra business computer; add --type recruiting|codeops|research|crm');
3320
4059
  console.log(' activate Attach EC2 to --workspace and remember it as the default');
3321
4060
  console.log(' chat Interactive cloud workspace chat');
3322
4061
  console.log(' chat --message Send one non-interactive message and print the reply');
@@ -3342,10 +4081,10 @@ async function runComputer() {
3342
4081
  console.log(' atris computer');
3343
4082
  console.log(' atris computer card --write');
3344
4083
  console.log(' atris business init "My Lab" # first/default computer with Atris + operator');
3345
- console.log(' atris computer create "Support Computer" --business <business>');
3346
- console.log(' atris computer --business <business> --workspace <workspace-id>');
3347
- console.log(' atris computer sleep --business <business> --workspace <workspace-id>');
3348
- console.log(' atris computer delete --business <business> --workspace <workspace-id>');
4084
+ console.log(' atris computer create "Hiring Computer" --business my-lab --type recruiting');
4085
+ console.log(' atris computer --business my-lab --workspace <workspace-id>');
4086
+ console.log(' atris computer sleep --business my-lab --workspace <workspace-id>');
4087
+ console.log(' atris computer delete --business my-lab --workspace <workspace-id>');
3349
4088
  console.log(' atris computer proof');
3350
4089
  console.log(' atris computer local');
3351
4090
  console.log(' atris computer codex');
@@ -3369,6 +4108,9 @@ async function runComputer() {
3369
4108
  if (sub === 'create') {
3370
4109
  return computerCreate(token, args.slice(1), cloudOptions);
3371
4110
  }
4111
+ if (sub === 'recruiting') {
4112
+ return runRecruitingComputerShortcut(token, args, cloudOptions);
4113
+ }
3372
4114
 
3373
4115
  const ctx = await resolveComputerCommandContext(token, cloudOptions);
3374
4116
 
@@ -3463,4 +4205,5 @@ module.exports = {
3463
4205
  renderComputerCardMarkdown,
3464
4206
  extractAttachedWorkspaceMismatch,
3465
4207
  contextForAttachedWorkspaceMismatch,
4208
+ printRecruitingLocalSyncOutcome,
3466
4209
  };