atris 3.35.0 → 3.37.0

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 (133) hide show
  1. package/AGENTS.md +37 -0
  2. package/README.md +5 -3
  3. package/atris/GETTING_STARTED.md +1 -1
  4. package/atris/atris.md +3 -0
  5. package/atris/policies/day-loop-voice.md +102 -0
  6. package/atris/policies/outbound-artifact-gate.md +2 -0
  7. package/atris/skills/design/SKILL.md +56 -32
  8. package/atris/skills/endgame/SKILL.md +12 -6
  9. package/atris/skills/engines/SKILL.md +22 -4
  10. package/atris/skills/fable-method/SKILL.md +66 -0
  11. package/atris/skills/improve/SKILL.md +65 -45
  12. package/atris/skills/youtube/SKILL.md +10 -1
  13. package/atris.md +2 -0
  14. package/ax +147 -19
  15. package/bin/atris.js +576 -267
  16. package/commands/activate.js +194 -88
  17. package/commands/agents.js +166 -0
  18. package/commands/autoland.js +459 -107
  19. package/commands/autopilot-front.js +20 -2
  20. package/commands/autopilot.js +118 -2
  21. package/commands/avail.js +407 -0
  22. package/commands/bench.js +188 -0
  23. package/commands/brain.js +3 -0
  24. package/commands/brief.js +651 -0
  25. package/commands/business-sync.js +192 -6
  26. package/commands/clean.js +50 -24
  27. package/commands/close.js +1083 -0
  28. package/commands/cloud.js +245 -0
  29. package/commands/compile.js +292 -1
  30. package/commands/computer.js +150 -3
  31. package/commands/dream.js +365 -0
  32. package/commands/drill.js +371 -0
  33. package/commands/engine.js +993 -32
  34. package/commands/experiments.js +28 -0
  35. package/commands/feedback.js +34 -12
  36. package/commands/fleet-report.js +206 -0
  37. package/commands/gm.js +23 -0
  38. package/commands/goal.js +247 -0
  39. package/commands/improve.js +642 -26
  40. package/commands/init.js +72 -44
  41. package/commands/interview.js +67 -1
  42. package/commands/land.js +152 -52
  43. package/commands/lifecycle.js +39 -3
  44. package/commands/log.js +84 -1
  45. package/commands/loops.js +220 -16
  46. package/commands/meet.js +220 -0
  47. package/commands/member.js +511 -34
  48. package/commands/mission.js +3144 -338
  49. package/commands/next.js +137 -0
  50. package/commands/now.js +220 -25
  51. package/commands/one-lap.js +776 -0
  52. package/commands/orb.js +314 -0
  53. package/commands/pack-craft.js +179 -0
  54. package/commands/pack.js +823 -0
  55. package/commands/play.js +3 -2
  56. package/commands/probe.js +30 -3
  57. package/commands/pulse.js +241 -46
  58. package/commands/push.js +260 -82
  59. package/commands/rainmaker.js +49 -0
  60. package/commands/report.js +415 -0
  61. package/commands/scout.js +147 -0
  62. package/commands/search.js +363 -0
  63. package/commands/skill.js +47 -3
  64. package/commands/slop.js +50 -2
  65. package/commands/soul.js +1 -1
  66. package/commands/stream.js +861 -0
  67. package/commands/study.js +693 -0
  68. package/commands/sync.js +67 -54
  69. package/commands/task.js +1346 -117
  70. package/commands/team.js +73 -0
  71. package/commands/verify.js +96 -0
  72. package/commands/watch.js +303 -0
  73. package/commands/wish.js +500 -0
  74. package/commands/workflow.js +11 -5
  75. package/commands/worktree.js +234 -13
  76. package/commands/xp.js +29 -11
  77. package/lib/auto-accept-certified.js +331 -34
  78. package/lib/autoland.js +319 -54
  79. package/lib/ax-auto-lane.js +96 -0
  80. package/lib/bench/context.js +147 -0
  81. package/lib/bench/engines.js +141 -0
  82. package/lib/bench/report.js +140 -0
  83. package/lib/bench/runner.js +512 -0
  84. package/lib/brief-ledger.js +350 -0
  85. package/lib/cloud-mission.js +259 -0
  86. package/lib/codex-flight.js +154 -0
  87. package/lib/default-runner.js +45 -0
  88. package/lib/default-verifier.js +70 -0
  89. package/lib/engine-registry.js +232 -0
  90. package/lib/experiments/daily.js +640 -0
  91. package/lib/fleet.js +2219 -67
  92. package/lib/improve-vitals-html.js +171 -0
  93. package/lib/known-commands.js +58 -0
  94. package/lib/loop-doctor.js +416 -0
  95. package/lib/member-switches.js +144 -0
  96. package/lib/mission-room.js +1 -0
  97. package/lib/mission-root.js +52 -0
  98. package/lib/next-moves.js +327 -10
  99. package/lib/one-lap-validator.js +60 -0
  100. package/lib/orb-context.js +477 -0
  101. package/lib/orb-scorecard.js +224 -0
  102. package/lib/policy-lessons.js +52 -1
  103. package/lib/pulse.js +277 -3
  104. package/lib/receipt-block.js +168 -0
  105. package/lib/receipt-evidence.js +65 -4
  106. package/lib/router-brain.js +352 -0
  107. package/lib/runner-command.js +10 -0
  108. package/lib/self-drive.js +258 -0
  109. package/lib/short-name.js +103 -0
  110. package/lib/spawn-env.js +18 -0
  111. package/lib/state-detection.js +56 -1
  112. package/lib/sync-status.js +59 -0
  113. package/lib/task-db.js +108 -29
  114. package/lib/task-proof.js +23 -1
  115. package/lib/team-presence.js +260 -0
  116. package/lib/tool-result-encode.js +7 -0
  117. package/lib/trust-tiers.js +90 -0
  118. package/lib/usage.js +107 -0
  119. package/lib/voice-gate.js +163 -0
  120. package/lib/wish-audit.js +1368 -0
  121. package/lib/wish-delegate.js +1840 -0
  122. package/lib/wish-design.js +110 -0
  123. package/lib/wish-stats.js +183 -0
  124. package/lib/wish-store.js +354 -0
  125. package/lib/zip.js +221 -0
  126. package/package.json +3 -1
  127. package/templates/loops/atris/loops/LOOPS.md +55 -0
  128. package/templates/loops/atris/loops/TICK.md +24 -0
  129. package/templates/loops/atris/loops/feedback.md +22 -0
  130. package/templates/loops/atris/loops/quality.md +22 -0
  131. package/templates/loops/atris/wiki/systems/loops.md +41 -0
  132. package/utils/api.js +5 -1
  133. package/utils/auth.js +57 -21
package/commands/loops.js CHANGED
@@ -1,8 +1,11 @@
1
1
  /**
2
- * atris loops - one view of every background loop: what runs, what died,
3
- * what you can start or stop.
2
+ * atris loops - project loop audit/scaffold plus legacy background loop board.
4
3
  *
5
- * atris loops - the board (registry jobs, launchd agents, missions)
4
+ * atris loops - audit atris/loops in this project
5
+ * atris loops init - scaffold the loop system into this project
6
+ * atris self-improve - alias for atris loops init
7
+ * atris loops tick - print the loop tick protocol
8
+ * atris loops board - legacy board: registry jobs, launchd agents, mission
6
9
  * atris loops stop <id> - disable a registry job or boot out a launchd agent
7
10
  * atris loops start <id> - enable a registry job or kickstart a launchd agent
8
11
  */
@@ -12,6 +15,16 @@ const os = require('os');
12
15
  const path = require('path');
13
16
  const { execSync, spawnSync } = require('child_process');
14
17
 
18
+ const MAX_LOG_AGE_DAYS = 14;
19
+ const TEMPLATE_ROOT = path.join(__dirname, '..', 'templates', 'loops');
20
+ const SCAFFOLD_FILES = [
21
+ 'atris/loops/LOOPS.md',
22
+ 'atris/loops/TICK.md',
23
+ 'atris/loops/feedback.md',
24
+ 'atris/loops/quality.md',
25
+ 'atris/wiki/systems/loops.md',
26
+ ];
27
+
15
28
  function heartbeatDir() {
16
29
  return process.env.ATRIS_HEARTBEAT_DIR || path.join(os.homedir(), '.atris', 'heartbeat');
17
30
  }
@@ -127,6 +140,7 @@ function showBoard(cwd) {
127
140
  console.log('');
128
141
  console.log('control: atris loops stop <id> · atris loops start <id>');
129
142
  console.log('ids: registry job id, or a com.atris.* launchd label');
143
+ return 0;
130
144
  }
131
145
 
132
146
  function saveRegistry(registry) {
@@ -178,35 +192,225 @@ function controlLoop(id, action) {
178
192
  process.exit(1);
179
193
  }
180
194
  console.log(`${action === 'stop' ? 'Stopped' : 'Started'} ${target}.`);
181
- return;
195
+ return 0;
182
196
  }
183
197
  const ok = setRegistryJob(target, action === 'start');
184
198
  if (!ok) {
185
- console.error(`No registry job named "${target}". Run: atris loops`);
199
+ console.error(`No registry job named "${target}". Run: atris loops board`);
186
200
  process.exit(1);
187
201
  }
188
202
  console.log(`${action === 'stop' ? 'Disabled' : 'Enabled'} ${target} in the heartbeat registry (backup written).`);
203
+ return 0;
204
+ }
205
+
206
+ function todayIso() {
207
+ return new Date().toISOString().slice(0, 10);
208
+ }
209
+
210
+ function renderTemplate(text) {
211
+ return text.split('{{DATE}}').join(todayIso());
212
+ }
213
+
214
+ function initLoops(cwd = process.cwd()) {
215
+ const created = [];
216
+ const skipped = [];
217
+
218
+ for (const relPath of SCAFFOLD_FILES) {
219
+ const source = path.join(TEMPLATE_ROOT, relPath);
220
+ const target = path.join(cwd, relPath);
221
+ if (fs.existsSync(target)) {
222
+ skipped.push(relPath);
223
+ continue;
224
+ }
225
+ const content = renderTemplate(fs.readFileSync(source, 'utf8'));
226
+ fs.mkdirSync(path.dirname(target), { recursive: true });
227
+ fs.writeFileSync(target, content, 'utf8');
228
+ created.push(relPath);
229
+ }
230
+
231
+ console.log('atris loops init');
232
+ console.log('');
233
+ console.log('created:');
234
+ if (created.length) created.forEach(file => console.log(` ${file}`));
235
+ else console.log(' none');
236
+ console.log('');
237
+ console.log('skipped:');
238
+ if (skipped.length) skipped.forEach(file => console.log(` ${file}`));
239
+ else console.log(' none');
240
+ console.log('');
241
+ console.log('Next: fill Owner/Wiki/Runner TODOs, then run atris loops audit');
242
+ return 0;
243
+ }
244
+
245
+ function stripAnchor(linkTarget) {
246
+ return String(linkTarget || '').split('#')[0];
247
+ }
248
+
249
+ function wikiTargetPath(loopFilePath, wikiLink) {
250
+ const cleaned = stripAnchor(wikiLink);
251
+ if (!cleaned || /^[a-z]+:\/\//i.test(cleaned)) return null;
252
+ return path.resolve(path.dirname(loopFilePath), cleaned);
253
+ }
254
+
255
+ function checkCommandFrom(text) {
256
+ return text.match(/\*\*Check:\*\*\s*`([^`]+)`/)?.[1]?.trim() || null;
257
+ }
258
+
259
+ function runLoopCheck(commandText, cwd) {
260
+ const result = spawnSync(commandText, {
261
+ cwd,
262
+ shell: true,
263
+ encoding: 'utf8',
264
+ timeout: 240000,
265
+ });
266
+ return {
267
+ command: commandText,
268
+ exitCode: typeof result.status === 'number' ? result.status : 1,
269
+ error: result.error ? result.error.message : null,
270
+ };
271
+ }
272
+
273
+ function structuralProblems({ root, filePath, text, nowMs }) {
274
+ const problems = [];
275
+
276
+ const owner = text.match(/\*\*Owner:\*\*\s*`team\/([^`]+)`/)?.[1]?.trim();
277
+ if (!owner) problems.push('no Owner field');
278
+ else if (!fs.existsSync(path.join(root, 'team', owner))) problems.push(`owner team/${owner} does not exist`);
279
+
280
+ const wiki = text.match(/\*\*Wiki:\*\*\s*\[[^\]]+\]\(([^)]+)\)/)?.[1]?.trim();
281
+ if (!wiki) {
282
+ problems.push('no Wiki field');
283
+ } else {
284
+ const wikiPath = wikiTargetPath(filePath, wiki);
285
+ if (!wikiPath || !fs.existsSync(wikiPath)) problems.push(`wiki page ${wiki} missing`);
286
+ }
287
+
288
+ if (!/\*\*Runner:\*\*\s*\S/.test(text)) problems.push('no Runner field');
289
+ if (!/\*\*Protects:\*\*/.test(text)) problems.push('no Protects field');
290
+ if (!/\*\*Signal/.test(text)) problems.push('no Signal field');
291
+ if (!/\*\*Cadence:\*\*/.test(text)) problems.push('no Cadence field');
292
+
293
+ const logDates = [...text.matchAll(/^- (\d{4}-\d{2}-\d{2}):/gm)].map(match => match[1]).sort();
294
+ if (logDates.length === 0) {
295
+ problems.push('no dated Log entries');
296
+ } else {
297
+ const newest = logDates[logDates.length - 1];
298
+ const ageDays = (nowMs - Date.parse(`${newest}T00:00:00Z`)) / 86400000;
299
+ if (ageDays > MAX_LOG_AGE_DAYS) problems.push(`log stale: newest ${newest}`);
300
+ }
301
+
302
+ return { owner, problems };
303
+ }
304
+
305
+ function auditLoops(cwd = process.cwd()) {
306
+ const loopsDir = path.join(cwd, 'atris', 'loops');
307
+ if (!fs.existsSync(loopsDir)) {
308
+ console.log('No atris/loops directory found.');
309
+ console.log('Run: atris loops init');
310
+ console.log('');
311
+ console.log('SELF-IMPROVING: NOT YET — 1 open');
312
+ return 1;
313
+ }
314
+
315
+ const files = fs.readdirSync(loopsDir)
316
+ .filter(file => file.endsWith('.md') && file !== 'LOOPS.md' && file !== 'TICK.md')
317
+ .sort();
318
+
319
+ if (!files.length) {
320
+ console.log('No loop files found in atris/loops.');
321
+ console.log('Run: atris loops init');
322
+ console.log('');
323
+ console.log('SELF-IMPROVING: NOT YET — 1 open');
324
+ return 1;
325
+ }
326
+
327
+ console.log('atris loops audit');
328
+ console.log('');
329
+
330
+ let open = 0;
331
+ const nowMs = Date.now();
332
+ for (const file of files) {
333
+ const filePath = path.join(loopsDir, file);
334
+ const text = fs.readFileSync(filePath, 'utf8');
335
+ const { problems } = structuralProblems({ root: cwd, filePath, text, nowMs });
336
+ const checkCommand = checkCommandFrom(text);
337
+ const check = checkCommand ? runLoopCheck(checkCommand, cwd) : null;
338
+ if (check && check.exitCode !== 0) problems.push(`check failed: exit ${check.exitCode}`);
339
+
340
+ if (problems.length) open += 1;
341
+ const loopName = file.replace(/\.md$/, '');
342
+ console.log(`${problems.length ? '❌' : '✅'} ${loopName}`);
343
+ for (const problem of problems) console.log(` ↳ ${problem}`);
344
+ if (check) {
345
+ const errorSuffix = check.error ? ` (${check.error})` : '';
346
+ console.log(` ↳ check: \`${check.command}\` exit ${check.exitCode}${errorSuffix}`);
347
+ }
348
+ }
349
+
350
+ console.log('');
351
+ if (open === 0) {
352
+ console.log('SELF-IMPROVING: YES');
353
+ return 0;
354
+ }
355
+ console.log(`SELF-IMPROVING: NOT YET — ${open} open`);
356
+ return 1;
357
+ }
358
+
359
+ function tickLoops(cwd = process.cwd()) {
360
+ const localTick = path.join(cwd, 'atris', 'loops', 'TICK.md');
361
+ const packagedTick = path.join(TEMPLATE_ROOT, 'atris', 'loops', 'TICK.md');
362
+ const source = fs.existsSync(localTick) ? localTick : packagedTick;
363
+ console.log(fs.readFileSync(source, 'utf8').trimEnd());
364
+ return 0;
365
+ }
366
+
367
+ function showLoopsHelp() {
368
+ console.log('Usage: atris loops [audit|init|tick|board|start|stop]');
369
+ console.log('');
370
+ console.log('Self-improving loop commands:');
371
+ console.log(' atris loops - audit atris/loops in this project');
372
+ console.log(' atris loops audit - same audit explicitly');
373
+ console.log(' atris loops init - scaffold atris/loops + loop wiki');
374
+ console.log(' atris self-improve - alias for atris loops init');
375
+ console.log(' atris loops tick - print atris/loops/TICK.md protocol');
376
+ console.log('');
377
+ console.log('Background loop board:');
378
+ console.log(' atris loops board - registry jobs, launchd agents, mission');
379
+ console.log(' atris loops stop <id> - disable a registry job or stop a launchd agent');
380
+ console.log(' atris loops start <id> - enable a registry job or start a launchd agent');
381
+ return 0;
189
382
  }
190
383
 
191
384
  function loopsCommand(subcommand, ...args) {
192
385
  switch (subcommand) {
193
386
  case undefined:
387
+ case 'audit':
388
+ return auditLoops(process.cwd());
389
+ case 'init':
390
+ return initLoops(process.cwd());
391
+ case 'tick':
392
+ return tickLoops(process.cwd());
194
393
  case 'board':
195
394
  case 'list':
196
- showBoard(process.cwd());
197
- break;
395
+ return showBoard(process.cwd());
198
396
  case 'stop':
199
- controlLoop(args[0], 'stop');
200
- break;
397
+ return controlLoop(args[0], 'stop');
201
398
  case 'start':
202
- controlLoop(args[0], 'start');
203
- break;
399
+ return controlLoop(args[0], 'start');
400
+ case 'help':
401
+ case '--help':
402
+ case '-h':
403
+ return showLoopsHelp();
204
404
  default:
205
- console.log('Loop commands:');
206
- console.log(' atris loops - board: registry jobs, launchd agents, mission');
207
- console.log(' atris loops stop <id> - disable a registry job or stop a launchd agent');
208
- console.log(' atris loops start <id> - enable a registry job or start a launchd agent');
405
+ return showLoopsHelp();
209
406
  }
210
407
  }
211
408
 
212
- module.exports = { loopsCommand, showBoard };
409
+ module.exports = {
410
+ loopsCommand,
411
+ showBoard,
412
+ initLoops,
413
+ auditLoops,
414
+ tickLoops,
415
+ structuralProblems,
416
+ };
@@ -0,0 +1,220 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { initAtris } = require('./init');
6
+ const { run: themeCommand } = require('./theme');
7
+ const { parseSimpleFlags, runPlainInterview } = require('./interview');
8
+ const {
9
+ parseTimeToDecimal,
10
+ parseDaysArg,
11
+ buildAvailableHours,
12
+ resolveTimezone,
13
+ } = require('./avail');
14
+ const { getAppBaseUrl } = require('../utils/api');
15
+
16
+ const VALUE_FLAGS = [
17
+ '--name',
18
+ '--want',
19
+ '--currency',
20
+ '--username',
21
+ '--wake',
22
+ '--sleep',
23
+ '--where',
24
+ '--tz',
25
+ '--days',
26
+ '--slot-minutes',
27
+ '--window-days',
28
+ ];
29
+
30
+ const MEET_FIELDS = [
31
+ { key: 'name', flag: '--name', question: 'who are you?\n> ' },
32
+ { key: 'want', flag: '--want', question: 'what do you want to make happen?\n> ' },
33
+ { key: 'currency', flag: '--currency', question: 'what would count as a win in your world?\n> ' },
34
+ ];
35
+
36
+ function showHelp() {
37
+ console.log('');
38
+ console.log('usage: atris meet --name <name> --want <want> --currency <win>');
39
+ console.log('');
40
+ console.log('onboard someone in one sitting, write their first profile, and print their /book link.');
41
+ console.log('');
42
+ console.log('options:');
43
+ console.log(' --name text who they are');
44
+ console.log(' --want text what they want');
45
+ console.log(' --currency text what a win looks like');
46
+ console.log(' --username text booking link name, defaults from --name');
47
+ console.log(' --wake time first bookable time, default 9am');
48
+ console.log(' --sleep time last bookable time, default 5pm');
49
+ console.log(' --days days all, weekdays, weekends, or mon,wed,fri');
50
+ console.log('');
51
+ }
52
+
53
+ function slugify(value, fallback = 'guest') {
54
+ const slug = String(value || '')
55
+ .toLowerCase()
56
+ .replace(/[^a-z0-9]+/g, '-')
57
+ .replace(/^-+|-+$/g, '')
58
+ .slice(0, 48);
59
+ return slug || fallback;
60
+ }
61
+
62
+ function positiveInt(value, fallback) {
63
+ const n = Number.parseInt(value, 10);
64
+ return Number.isFinite(n) && n > 0 ? n : fallback;
65
+ }
66
+
67
+ async function withQuietOutput(fn) {
68
+ const originalLog = console.log;
69
+ const originalError = console.error;
70
+ console.log = () => {};
71
+ console.error = () => {};
72
+ try {
73
+ return await fn();
74
+ } finally {
75
+ console.log = originalLog;
76
+ console.error = originalError;
77
+ }
78
+ }
79
+
80
+ function ensureAtrisWorkspace(root) {
81
+ if (fs.existsSync(path.join(root, 'atris'))) return;
82
+
83
+ const originalArgv = process.argv;
84
+ process.argv = [originalArgv[0], originalArgv[1], 'init', '--yes'];
85
+ const cwd = process.cwd();
86
+ process.chdir(root);
87
+ try {
88
+ initAtris();
89
+ } finally {
90
+ process.chdir(cwd);
91
+ process.argv = originalArgv;
92
+ }
93
+ }
94
+
95
+ async function ensureTheme(root) {
96
+ if (fs.existsSync(path.join(root, '.atris', 'theme.json'))) return 0;
97
+ const cwd = process.cwd();
98
+ process.chdir(root);
99
+ try {
100
+ return await themeCommand(['init']);
101
+ } finally {
102
+ process.chdir(cwd);
103
+ }
104
+ }
105
+
106
+ function buildBookingProfile(options = {}) {
107
+ const wake = options.wake || '9am';
108
+ const sleep = options.sleep || '5pm';
109
+ const wakeDec = parseTimeToDecimal(wake);
110
+ const sleepDec = parseTimeToDecimal(sleep);
111
+ if (!(wakeDec < sleepDec)) throw new Error('wake must be before sleep on the same day');
112
+
113
+ const days = options.days || 'weekdays';
114
+ const dayIndexes = parseDaysArg(days);
115
+ const timezone = resolveTimezone({ tz: options.tz, where: options.where });
116
+
117
+ return {
118
+ days,
119
+ wake,
120
+ sleep,
121
+ settings: {
122
+ timezone,
123
+ available_hours: buildAvailableHours(dayIndexes, wakeDec, sleepDec),
124
+ slot_duration_minutes: positiveInt(options.slotMinutes, 30),
125
+ booking_window_days: positiveInt(options.windowDays, 30),
126
+ },
127
+ };
128
+ }
129
+
130
+ function renderProfile({ name, want, currency, link, username, booking }) {
131
+ return [
132
+ '# profile',
133
+ '',
134
+ `name: ${name}`,
135
+ `booking link: ${link}`,
136
+ `currency: ${currency}`,
137
+ '',
138
+ '## first wish',
139
+ '',
140
+ `- said: ${want}`,
141
+ `- win means: ${currency}`,
142
+ '',
143
+ '## booking page',
144
+ '',
145
+ `- username: ${username}`,
146
+ `- days: ${booking.days}`,
147
+ `- hours: ${booking.wake} to ${booking.sleep}`,
148
+ `- timezone: ${booking.settings.timezone}`,
149
+ '',
150
+ ].join('\n');
151
+ }
152
+
153
+ function writeMeetFiles(root, profile) {
154
+ const atrisDir = path.join(root, 'atris');
155
+ fs.mkdirSync(atrisDir, { recursive: true });
156
+ fs.writeFileSync(path.join(atrisDir, 'profile.md'), renderProfile(profile), 'utf8');
157
+
158
+ const localConfigDir = path.join(root, '.atris');
159
+ fs.mkdirSync(localConfigDir, { recursive: true });
160
+ fs.writeFileSync(path.join(localConfigDir, 'booking-settings.json'), JSON.stringify({
161
+ username: profile.username,
162
+ link: profile.link,
163
+ settings: profile.booking.settings,
164
+ }, null, 2) + '\n', 'utf8');
165
+ }
166
+
167
+ async function meetCommand(args = [], root = process.cwd()) {
168
+ if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
169
+ showHelp();
170
+ return 0;
171
+ }
172
+
173
+ const interview = await runPlainInterview({ args, fields: MEET_FIELDS });
174
+ if (!interview.ok) {
175
+ console.error(`meet needs ${interview.missing.join(', ')} when not running in a terminal.`);
176
+ return 2;
177
+ }
178
+
179
+ const parsed = parseSimpleFlags(args, VALUE_FLAGS);
180
+ const name = interview.answers.name;
181
+ const want = interview.answers.want;
182
+ const currency = interview.answers.currency;
183
+ const username = slugify(parsed.values['--username'] || name);
184
+ const link = `${getAppBaseUrl()}/book/${username}`;
185
+
186
+ let booking;
187
+ try {
188
+ booking = buildBookingProfile({
189
+ wake: parsed.values['--wake'],
190
+ sleep: parsed.values['--sleep'],
191
+ where: parsed.values['--where'],
192
+ tz: parsed.values['--tz'],
193
+ days: parsed.values['--days'],
194
+ slotMinutes: parsed.values['--slot-minutes'],
195
+ windowDays: parsed.values['--window-days'],
196
+ });
197
+ } catch (err) {
198
+ console.error(err.message || String(err));
199
+ return 1;
200
+ }
201
+
202
+ await withQuietOutput(async () => {
203
+ ensureAtrisWorkspace(root);
204
+ const themeCode = await ensureTheme(root);
205
+ if (themeCode) throw new Error('theme setup failed');
206
+ });
207
+
208
+ writeMeetFiles(root, { name, want, currency, username, link, booking });
209
+
210
+ console.log(link);
211
+ console.log(`next: share this link; atris will start turning new conversations into ${currency}.`);
212
+ return 0;
213
+ }
214
+
215
+ module.exports = {
216
+ meetCommand,
217
+ slugify,
218
+ buildBookingProfile,
219
+ renderProfile,
220
+ };