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.
- package/AGENTS.md +6 -0
- package/atris/AGENTS.md +11 -0
- package/atris/CLAUDE.md +5 -0
- package/atris/atris.md +19 -4
- package/atris/policies/atris-design.md +71 -0
- package/atris/policies/design-seed.md +187 -0
- package/atris/skills/atris/SKILL.md +26 -3
- package/atris/skills/design/SKILL.md +3 -2
- package/atris/skills/loop/SKILL.md +5 -3
- package/atris/team/_template/MEMBER.md +19 -0
- package/atris.md +63 -23
- package/ax +1434 -1698
- package/bin/atris.js +5 -1
- package/commands/agent-spawn.js +2 -2
- package/commands/brain.js +92 -7
- package/commands/brainstorm.js +62 -22
- package/commands/business-sync.js +92 -8
- package/commands/business.js +13 -7
- package/commands/chat-scan.js +102 -0
- package/commands/computer.js +758 -15
- package/commands/deck.js +505 -105
- package/commands/init.js +6 -1
- package/commands/launchpad.js +638 -0
- package/commands/log-sync.js +44 -13
- package/commands/loop-front.js +290 -0
- package/commands/member.js +124 -3
- package/commands/mission.js +653 -30
- package/commands/pull.js +37 -28
- package/commands/pulse.js +11 -8
- package/commands/run.js +79 -39
- package/commands/sync.js +36 -17
- package/commands/task.js +1072 -89
- package/commands/workflow.js +6 -6
- package/commands/xp.js +13 -1
- package/commands/youtube.js +221 -7
- package/decks/README.md +89 -0
- package/decks/archetype-catalog.json +180 -0
- package/decks/atris-antislop-pitch.json +48 -0
- package/decks/atris-archetypes-v2.json +83 -0
- package/decks/atris-one-loop-pitch.json +80 -0
- package/decks/atris-seed-pitch-v3.json +118 -0
- package/decks/atris-seed-pitch-v4-skeleton.json +106 -0
- package/decks/atris-seed-pitch-v5.json +109 -0
- package/decks/atris-seed-pitch-v6.json +137 -0
- package/decks/atris-seed-pitch-v7.json +133 -0
- package/decks/atris-single-shot-proof.json +74 -0
- package/decks/mark-pincus-narrative.json +102 -0
- package/decks/mark-pincus-sourcery.json +94 -0
- package/decks/yash-applied-compute-detailed.json +150 -0
- package/decks/yash-applied-compute-generalist.json +82 -0
- package/decks/yash-applied-compute-narrative.json +54 -0
- package/lib/ax-prefs.js +5 -12
- package/lib/chat-log-scan.js +377 -0
- package/lib/context-gatherer.js +35 -1
- package/lib/deck-compose.js +145 -0
- package/lib/deck-history.js +64 -0
- package/lib/deck-layout.js +169 -0
- package/lib/deck-review.js +431 -0
- package/lib/deck-schema.js +154 -0
- package/lib/file-ops.js +3 -3
- package/lib/functional-owner.js +189 -0
- package/lib/journal.js +7 -73
- package/lib/slides-deck.js +512 -58
- package/lib/task-db.js +128 -11
- package/package.json +2 -1
- package/templates/business-starter/team/START_HERE.md +12 -8
- package/utils/auth.js +4 -0
- package/utils/config.js +4 -0
- package/atris/atrisDev.md +0 -717
package/commands/computer.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
1605
|
-
console.log(' atris computer create "
|
|
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:
|
|
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
|
|
1814
|
-
console.log(' atris computer delete --business
|
|
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
|
-
|
|
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 "
|
|
3346
|
-
console.log(' atris computer --business
|
|
3347
|
-
console.log(' atris computer sleep --business
|
|
3348
|
-
console.log(' atris computer delete --business
|
|
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
|
};
|