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.
- package/AGENTS.md +37 -0
- package/README.md +5 -3
- package/atris/GETTING_STARTED.md +1 -1
- package/atris/atris.md +3 -0
- package/atris/policies/day-loop-voice.md +102 -0
- package/atris/policies/outbound-artifact-gate.md +2 -0
- package/atris/skills/design/SKILL.md +56 -32
- package/atris/skills/endgame/SKILL.md +12 -6
- package/atris/skills/engines/SKILL.md +22 -4
- package/atris/skills/fable-method/SKILL.md +66 -0
- package/atris/skills/improve/SKILL.md +65 -45
- package/atris/skills/youtube/SKILL.md +10 -1
- package/atris.md +2 -0
- package/ax +147 -19
- package/bin/atris.js +576 -267
- package/commands/activate.js +194 -88
- package/commands/agents.js +166 -0
- package/commands/autoland.js +459 -107
- package/commands/autopilot-front.js +20 -2
- package/commands/autopilot.js +118 -2
- package/commands/avail.js +407 -0
- package/commands/bench.js +188 -0
- package/commands/brain.js +3 -0
- package/commands/brief.js +651 -0
- package/commands/business-sync.js +192 -6
- package/commands/clean.js +50 -24
- package/commands/close.js +1083 -0
- package/commands/cloud.js +245 -0
- package/commands/compile.js +292 -1
- package/commands/computer.js +150 -3
- package/commands/dream.js +365 -0
- package/commands/drill.js +371 -0
- package/commands/engine.js +993 -32
- package/commands/experiments.js +28 -0
- package/commands/feedback.js +34 -12
- package/commands/fleet-report.js +206 -0
- package/commands/gm.js +23 -0
- package/commands/goal.js +247 -0
- package/commands/improve.js +642 -26
- package/commands/init.js +72 -44
- package/commands/interview.js +67 -1
- package/commands/land.js +152 -52
- package/commands/lifecycle.js +39 -3
- package/commands/log.js +84 -1
- package/commands/loops.js +220 -16
- package/commands/meet.js +220 -0
- package/commands/member.js +511 -34
- package/commands/mission.js +3144 -338
- package/commands/next.js +137 -0
- package/commands/now.js +220 -25
- package/commands/one-lap.js +776 -0
- package/commands/orb.js +314 -0
- package/commands/pack-craft.js +179 -0
- package/commands/pack.js +823 -0
- package/commands/play.js +3 -2
- package/commands/probe.js +30 -3
- package/commands/pulse.js +241 -46
- package/commands/push.js +260 -82
- package/commands/rainmaker.js +49 -0
- package/commands/report.js +415 -0
- package/commands/scout.js +147 -0
- package/commands/search.js +363 -0
- package/commands/skill.js +47 -3
- package/commands/slop.js +50 -2
- package/commands/soul.js +1 -1
- package/commands/stream.js +861 -0
- package/commands/study.js +693 -0
- package/commands/sync.js +67 -54
- package/commands/task.js +1346 -117
- package/commands/team.js +73 -0
- package/commands/verify.js +96 -0
- package/commands/watch.js +303 -0
- package/commands/wish.js +500 -0
- package/commands/workflow.js +11 -5
- package/commands/worktree.js +234 -13
- package/commands/xp.js +29 -11
- package/lib/auto-accept-certified.js +331 -34
- package/lib/autoland.js +319 -54
- package/lib/ax-auto-lane.js +96 -0
- package/lib/bench/context.js +147 -0
- package/lib/bench/engines.js +141 -0
- package/lib/bench/report.js +140 -0
- package/lib/bench/runner.js +512 -0
- package/lib/brief-ledger.js +350 -0
- package/lib/cloud-mission.js +259 -0
- package/lib/codex-flight.js +154 -0
- package/lib/default-runner.js +45 -0
- package/lib/default-verifier.js +70 -0
- package/lib/engine-registry.js +232 -0
- package/lib/experiments/daily.js +640 -0
- package/lib/fleet.js +2219 -67
- package/lib/improve-vitals-html.js +171 -0
- package/lib/known-commands.js +58 -0
- package/lib/loop-doctor.js +416 -0
- package/lib/member-switches.js +144 -0
- package/lib/mission-room.js +1 -0
- package/lib/mission-root.js +52 -0
- package/lib/next-moves.js +327 -10
- package/lib/one-lap-validator.js +60 -0
- package/lib/orb-context.js +477 -0
- package/lib/orb-scorecard.js +224 -0
- package/lib/policy-lessons.js +52 -1
- package/lib/pulse.js +277 -3
- package/lib/receipt-block.js +168 -0
- package/lib/receipt-evidence.js +65 -4
- package/lib/router-brain.js +352 -0
- package/lib/runner-command.js +10 -0
- package/lib/self-drive.js +258 -0
- package/lib/short-name.js +103 -0
- package/lib/spawn-env.js +18 -0
- package/lib/state-detection.js +56 -1
- package/lib/sync-status.js +59 -0
- package/lib/task-db.js +108 -29
- package/lib/task-proof.js +23 -1
- package/lib/team-presence.js +260 -0
- package/lib/tool-result-encode.js +7 -0
- package/lib/trust-tiers.js +90 -0
- package/lib/usage.js +107 -0
- package/lib/voice-gate.js +163 -0
- package/lib/wish-audit.js +1368 -0
- package/lib/wish-delegate.js +1840 -0
- package/lib/wish-design.js +110 -0
- package/lib/wish-stats.js +183 -0
- package/lib/wish-store.js +354 -0
- package/lib/zip.js +221 -0
- package/package.json +3 -1
- package/templates/loops/atris/loops/LOOPS.md +55 -0
- package/templates/loops/atris/loops/TICK.md +24 -0
- package/templates/loops/atris/loops/feedback.md +22 -0
- package/templates/loops/atris/loops/quality.md +22 -0
- package/templates/loops/atris/wiki/systems/loops.md +41 -0
- package/utils/api.js +5 -1
- package/utils/auth.js +57 -21
package/commands/loops.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* atris loops -
|
|
3
|
-
* what you can start or stop.
|
|
2
|
+
* atris loops - project loop audit/scaffold plus legacy background loop board.
|
|
4
3
|
*
|
|
5
|
-
* atris loops -
|
|
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
|
-
|
|
399
|
+
return controlLoop(args[0], 'start');
|
|
400
|
+
case 'help':
|
|
401
|
+
case '--help':
|
|
402
|
+
case '-h':
|
|
403
|
+
return showLoopsHelp();
|
|
204
404
|
default:
|
|
205
|
-
|
|
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 = {
|
|
409
|
+
module.exports = {
|
|
410
|
+
loopsCommand,
|
|
411
|
+
showBoard,
|
|
412
|
+
initLoops,
|
|
413
|
+
auditLoops,
|
|
414
|
+
tickLoops,
|
|
415
|
+
structuralProblems,
|
|
416
|
+
};
|
package/commands/meet.js
ADDED
|
@@ -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
|
+
};
|