atris 3.15.57 → 3.16.1

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 (47) hide show
  1. package/AGENTS.md +2 -2
  2. package/GETTING_STARTED.md +1 -1
  3. package/PERSONA.md +4 -4
  4. package/README.md +12 -11
  5. package/atris/skills/copy-editor/SKILL.md +30 -4
  6. package/atris/skills/improve/SKILL.md +18 -20
  7. package/atris/wiki/concepts/agent-activation-contract.md +5 -3
  8. package/atris/wiki/concepts/workspace-initialization-contract.md +4 -4
  9. package/atris/wiki/index.md +1 -0
  10. package/ax +522 -73
  11. package/bin/atris.js +78 -44
  12. package/commands/align.js +0 -14
  13. package/commands/apps.js +102 -1
  14. package/commands/autopilot.js +628 -31
  15. package/commands/brain.js +219 -34
  16. package/commands/brainstorm.js +0 -829
  17. package/commands/compile.js +569 -0
  18. package/commands/computer.js +0 -60
  19. package/commands/improve.js +501 -0
  20. package/commands/integrations.js +233 -71
  21. package/commands/lesson.js +44 -0
  22. package/commands/member.js +4498 -226
  23. package/commands/mission.js +302 -27
  24. package/commands/now.js +89 -1
  25. package/commands/probe.js +366 -0
  26. package/commands/radar.js +181 -56
  27. package/commands/recap.js +203 -0
  28. package/commands/skill.js +6 -2
  29. package/commands/soul.js +0 -4
  30. package/commands/task.js +5587 -499
  31. package/commands/terminal.js +14 -10
  32. package/commands/wiki.js +87 -1
  33. package/commands/workflow.js +288 -73
  34. package/commands/worktree.js +52 -15
  35. package/commands/xp.js +6 -65
  36. package/lib/auto-accept-certified.js +294 -0
  37. package/lib/file-ops.js +0 -184
  38. package/lib/member-alive.js +232 -0
  39. package/lib/policy-lessons.js +280 -0
  40. package/lib/receipt-evidence.js +64 -0
  41. package/lib/state-detection.js +75 -1
  42. package/lib/task-db.js +568 -16
  43. package/lib/task-proof.js +43 -0
  44. package/package.json +1 -1
  45. package/utils/auth.js +13 -4
  46. package/commands/research.js +0 -52
  47. package/lib/section-merge.js +0 -196
package/bin/atris.js CHANGED
@@ -67,8 +67,8 @@ const ensureValidCredentials = (opts) => _ensureValidCredentials(apiRequestJson,
67
67
  const fetchMyAgents = (token) => _fetchMyAgents(token, apiRequestJson);
68
68
  const displayAccountSummary = () => _displayAccountSummary(apiRequestJson);
69
69
 
70
- // Run update check in background (non-blocking)
71
- // Skip for 'version', 'update', and help commands to avoid redundant messages or help side effects.
70
+ // Run update check in background (non-blocking).
71
+ // Skip for machine-readable JSON and help commands to avoid corrupting stdout.
72
72
  let updateCheckPromise = null;
73
73
  const updateCommand = process.argv[2];
74
74
  const updateArgs = process.argv.slice(3);
@@ -78,7 +78,8 @@ const helpRequested = updateCommand === 'help'
78
78
  || updateArgs.includes('--help')
79
79
  || updateArgs.includes('-h')
80
80
  || updateArgs[0] === 'help';
81
- const skipUpdateCheck = Boolean(process.env.ATRIS_SKIP_UPDATE_CHECK || process.env.NO_UPDATE_NOTIFIER || helpRequested);
81
+ const jsonRequested = updateArgs.includes('--json');
82
+ const skipUpdateCheck = Boolean(process.env.ATRIS_SKIP_UPDATE_CHECK || process.env.NO_UPDATE_NOTIFIER || helpRequested || jsonRequested);
82
83
  if (!skipUpdateCheck && (!updateCommand || (updateCommand && !['version', 'update'].includes(updateCommand)))) {
83
84
  updateCheckPromise = checkForUpdates()
84
85
  .then((updateInfo) => {
@@ -117,15 +118,6 @@ const isBusinessSyncSafetyCommand = command === 'sync'
117
118
  || firstCommandArg === 'resolve'
118
119
  );
119
120
 
120
- // Keep APP.md app-pack operations independent from the heavier workspace boot
121
- // path so `atris apps --json` stays machine-readable for agents.
122
- if (command === 'apps') {
123
- const subcommand = process.argv[3];
124
- const args = process.argv.slice(4);
125
- require('../commands/apps').appsCommand(subcommand, ...args);
126
- process.exit(0);
127
- }
128
-
129
121
  // Auto-sync skills only for commands that modify workspace state
130
122
  if (['init', 'update', 'upgrade'].includes(command) || (command === 'sync' && !isBusinessSyncSafetyCommand)) {
131
123
  try {
@@ -336,6 +328,7 @@ function showHelp() {
336
328
  console.log(' radar - Show live agents joined with tasks, missions, and worktrees');
337
329
  console.log(' ctop - Show a process-first live agent CPU/memory view');
338
330
  console.log(' status - See local work and completions (`atris status <business>` for remote)');
331
+ console.log(' recap - What your AI team did, in plain English (--share for paste-ready)');
339
332
  console.log(' xp - Show Career XP and contribution graph');
340
333
  console.log(' analytics - Show recent productivity from journals');
341
334
  console.log(' search - Search journal history (atris search <keyword>)');
@@ -346,7 +339,7 @@ function showHelp() {
346
339
  console.log(' release - Tag release, bump version, create GitHub release, draft /launch');
347
340
  console.log(' learn - Project learnings (patterns, pitfalls, preferences)');
348
341
  console.log(' brain - Compile MAP/TODO/wiki/state into a loadable agent brain');
349
- console.log(' lesson - Append a one-line lesson to atris/lessons.md');
342
+ console.log(' lesson - Append a one-line lesson to atris/lessons.md (mine: distill receipts/episodes/scorecards into policy lessons)');
350
343
  console.log(' ingest - Local-first wiki ingest into atris/wiki/');
351
344
  console.log(' query - Local-first wiki query against atris/wiki/');
352
345
  console.log(' lint - Local-first wiki lint for atris/wiki/');
@@ -355,6 +348,7 @@ function showHelp() {
355
348
  console.log('Optional helpers:');
356
349
  console.log(' brainstorm - Explore ideas conversationally before planning');
357
350
  console.log(' autopilot - Guided loop that can clarify TODOs and run plan → do → review');
351
+ console.log(' improve - Run one paid RL tick (POST /api/improve, deducts credits)');
358
352
  console.log(' worktree - Isolated Git worktrees plus guarded ship/merge for parallel agents');
359
353
  console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
360
354
  console.log('');
@@ -364,6 +358,13 @@ function showHelp() {
364
358
  console.log(' experiments run <slug> - Execute a pack or record an Endstate receipt');
365
359
  console.log(' experiments benchmark [m] - Run validate/runtime experiment benchmarks');
366
360
  console.log('');
361
+ console.log('Compile loop (learn like AI, run like code):');
362
+ console.log(' compile record <name> - Append an execution record (--input/--output)');
363
+ console.log(' compile build <name> - Compile records into a deterministic run.js');
364
+ console.log(' compile backtest <name> - Replay all records, score accuracy vs gate');
365
+ console.log(' compile promote <name> - Activate (gate: accuracy >= threshold)');
366
+ console.log(' compile exec <name> - Run the compiled process token-free');
367
+ console.log('');
367
368
  console.log('Quick commands:');
368
369
  console.log(' atris - Load context and start (natural language)');
369
370
  console.log(' next - Auto-advance to next step');
@@ -491,14 +492,18 @@ function showDoHelp() {
491
492
 
492
493
  function showReviewHelp() {
493
494
  console.log('');
494
- console.log('Usage: atris review [--execute] [--full]');
495
+ console.log('Usage: atris review [--limit N|--all|--json] [--full|--execute]');
495
496
  console.log('');
496
497
  console.log('Description:');
497
- console.log(' Activate the Validator agent to verify recent changes.');
498
- console.log(' Reads TODO.md, MAP.md, and today\'s journal, then prints a validation');
499
- console.log(' checklist (and, in agent mode, runs tests and updates docs).');
498
+ console.log(' Show the certified Review queue: proof-ready work waiting for');
499
+ console.log(' human accept or revise. Human accept is the AgentXP gate.');
500
+ console.log(' Use --full/--verbose for the legacy Validator prompt.');
500
501
  console.log('');
501
502
  console.log('Options:');
503
+ console.log(' --limit N Show at most N certified review rows.');
504
+ console.log(' --all Show all certified review rows.');
505
+ console.log(' --json Emit the task-backed review queue as JSON.');
506
+ console.log(' --group-by Group certified rows by tag, owner, or source.');
502
507
  console.log(' --execute Run in agent mode via Atris cloud (requires login + agent).');
503
508
  console.log(' --full Print full spec/context dumps (verbose copy/paste).');
504
509
  console.log(' --verbose Alias for --full.');
@@ -769,9 +774,9 @@ if (command === '2' && ['fast', 'pro'].includes(String(firstCommandArg || '').to
769
774
  const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
770
775
  'activate', '_activate', 'agent', 'chat', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
771
776
  'clean', 'verify', 'search', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync',
772
- 'ingest', 'query', 'lint', 'loop', 'task', 'mission', 'worktree', 'aeo', 'xp', 'play', 'gm', 'x',
777
+ 'ingest', 'query', 'lint', 'loop', 'task', 'mission', 'probe', 'worktree', 'aeo', 'improve', 'xp', 'play', 'gm', 'x', 'recap',
773
778
  'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
774
- 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet'];
779
+ 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'compile'];
775
780
 
776
781
  // Check if command is an atris.md spec file - triggers welcome visualization
777
782
  function isSpecFile(cmd) {
@@ -1044,8 +1049,7 @@ async function interactiveEntry(userInput) {
1044
1049
 
1045
1050
  // ASCII Welcome Visualization
1046
1051
  function showWelcomeVisualization() {
1047
- const { getBacklogTasks, getInProgressTasks } = require('../lib/state-detection');
1048
- const { getLogPath, ensureLogDirectory, createLogFile } = require('../lib/journal');
1052
+ const { getTaskCounts } = require('../lib/state-detection');
1049
1053
  const cwd = process.cwd();
1050
1054
  const atrisDir = path.join(cwd, 'atris');
1051
1055
  const projectName = path.basename(cwd);
@@ -1054,21 +1058,13 @@ function showWelcomeVisualization() {
1054
1058
  let filesIndexed = 0;
1055
1059
  let tasksInBacklog = 0;
1056
1060
  let tasksInProgress = 0;
1061
+ let tasksInReview = 0;
1062
+ let tasksCertified = 0;
1057
1063
  let journalEntries = 0;
1058
1064
  let hasMap = false;
1059
1065
  let isInitialized = fs.existsSync(atrisDir);
1060
1066
 
1061
1067
  if (isInitialized) {
1062
- // Auto-create today's journal if missing
1063
- try {
1064
- ensureLogDirectory();
1065
- const { logFile, dateFormatted } = getLogPath();
1066
- if (!fs.existsSync(logFile)) {
1067
- createLogFile(logFile, dateFormatted);
1068
- }
1069
- } catch {
1070
- // Silently fail - don't block welcome display
1071
- }
1072
1068
  // Check MAP.md
1073
1069
  const mapPath = path.join(atrisDir, 'MAP.md');
1074
1070
  if (fs.existsSync(mapPath)) {
@@ -1079,15 +1075,15 @@ function showWelcomeVisualization() {
1079
1075
  filesIndexed = fileRefs ? fileRefs.length : 0;
1080
1076
  }
1081
1077
 
1082
- // Check TODO.md
1083
- const todoPath = path.join(atrisDir, 'TODO.md');
1084
- if (fs.existsSync(todoPath)) {
1085
- try {
1086
- tasksInBacklog = getBacklogTasks(atrisDir).length;
1087
- tasksInProgress = getInProgressTasks(atrisDir).length;
1088
- } catch {
1089
- // Silently fail - show 0 tasks if parsing fails
1090
- }
1078
+ // Task lane counts — DB truth first, TODO.md parse as fallback
1079
+ try {
1080
+ const counts = getTaskCounts(atrisDir);
1081
+ tasksInBacklog = counts.backlog;
1082
+ tasksInProgress = counts.active;
1083
+ tasksInReview = counts.review;
1084
+ tasksCertified = counts.reviewCertified;
1085
+ } catch {
1086
+ // Silently fail - show 0 tasks if reading fails
1091
1087
  }
1092
1088
 
1093
1089
  // Count journal entries today
@@ -1136,13 +1132,19 @@ function showWelcomeVisualization() {
1136
1132
  console.log(` │ 📄 Spec: atris.md v${CLI_VERSION.padEnd(18)}│`);
1137
1133
  console.log(` │ 🗺️ Map: ${hasMap ? (filesIndexed + ' files indexed').padEnd(26) : 'not generated yet'.padEnd(26)}│`);
1138
1134
  console.log(` │ 📋 Tasks: ${(tasksInBacklog + ' backlog, ' + tasksInProgress + ' active').padEnd(26)}│`);
1135
+ if (tasksInReview > 0) {
1136
+ const reviewText = tasksCertified > 0
1137
+ ? `${tasksInReview} waiting (${tasksCertified} certified)`
1138
+ : `${tasksInReview} waiting`;
1139
+ console.log(` │ ⏳ Review: ${reviewText.padEnd(26)}│`);
1140
+ }
1139
1141
  console.log(` │ 📝 Journal: ${(journalEntries + ' entries today').padEnd(26)}│`);
1140
1142
  console.log(' │ │');
1141
1143
  console.log(' │ ┌──────────────────────────────────┐ │');
1142
1144
  console.log(' │ │ MAP.md ←──── YOU ARE HERE │ │');
1143
1145
  console.log(' │ │ ↓ │ │');
1144
- const taskText = `${tasksInBacklog} tasks waiting`;
1145
- console.log(` │ │ TODO.md ←── ${taskText.padEnd(17)}│ │`);
1146
+ const taskText = `${tasksInBacklog} task${tasksInBacklog === 1 ? '' : 's'} waiting`;
1147
+ console.log(` │ │ TODO.md ←── ${taskText.padEnd(20)}│ │`);
1146
1148
  console.log(' │ │ ↓ │ │');
1147
1149
  console.log(' │ │ navigator → executor → validator│ │');
1148
1150
  console.log(' │ └──────────────────────────────────┘ │');
@@ -1150,7 +1152,11 @@ function showWelcomeVisualization() {
1150
1152
  console.log(' └──────────────────────────────────────────┘');
1151
1153
  }
1152
1154
  console.log('');
1153
- console.log(` Ready. Run 'atris plan' to start.`);
1155
+ if (tasksCertified > 0) {
1156
+ console.log(` Ready. ${tasksCertified} certified await accept — run 'atris task reviews'.`);
1157
+ } else {
1158
+ console.log(` Ready. Run 'atris plan' to start.`);
1159
+ }
1154
1160
  console.log('');
1155
1161
  }
1156
1162
 
@@ -1179,6 +1185,11 @@ if (command === 'init') {
1179
1185
  Promise.resolve(require('../commands/mission').missionCommand(process.argv.slice(3)))
1180
1186
  .then(() => process.exit(0))
1181
1187
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1188
+ } else if (command === 'probe') {
1189
+ // Chat-lane probe (TRR-22): one real /atris2/turn over the full tool relay.
1190
+ Promise.resolve(require('../commands/probe').probeCommand(process.argv.slice(3)))
1191
+ .then((code) => process.exit(code || 0))
1192
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1182
1193
  } else if (command === 'worktree') {
1183
1194
  Promise.resolve(require('../commands/worktree').worktreeCommand(process.argv.slice(3)))
1184
1195
  .then((code) => process.exit(code || 0))
@@ -1197,6 +1208,11 @@ if (command === 'init') {
1197
1208
  Promise.resolve(require('../commands/aeo').run(process.argv.slice(3)))
1198
1209
  .then(() => process.exit(0))
1199
1210
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1211
+ } else if (command === 'improve') {
1212
+ // Improve: one paid RL tick via POST /api/improve (deducts credits), local autopilot fallback.
1213
+ Promise.resolve(require('../commands/improve').run(process.argv.slice(3)))
1214
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1215
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1200
1216
  } else if (command === 'brain') {
1201
1217
  Promise.resolve()
1202
1218
  .then(() => require('../commands/brain').brainCommand(process.argv.slice(3)))
@@ -1243,6 +1259,8 @@ if (command === 'init') {
1243
1259
  process.exit(0);
1244
1260
  }
1245
1261
  require('../commands/now').nowAtris(args);
1262
+ } else if (command === 'recap') {
1263
+ require('../commands/recap').recapAtris(process.argv.slice(3));
1246
1264
  } else if (command === 'activate') {
1247
1265
  const args = process.argv.slice(3);
1248
1266
  if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
@@ -1622,6 +1640,14 @@ if (command === 'init') {
1622
1640
  integrationsStatus()
1623
1641
  .then(() => process.exit(0))
1624
1642
  .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
1643
+ } else if (command === 'apps') {
1644
+ // Keep APP.md app-pack operations independent from the heavier workspace boot
1645
+ // path so `atris apps --json` stays machine-readable for agents.
1646
+ const subcommand = process.argv[3];
1647
+ const args = process.argv.slice(4);
1648
+ Promise.resolve(require('../commands/apps').appsCommand(subcommand, ...args))
1649
+ .then(() => process.exit(0))
1650
+ .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
1625
1651
  } else if (command === 'learn') {
1626
1652
  const subcommand = process.argv[3];
1627
1653
  const args = process.argv.slice(4);
@@ -1637,7 +1663,9 @@ if (command === 'init') {
1637
1663
  } else if (command === 'member') {
1638
1664
  const subcommand = process.argv[3];
1639
1665
  const args = process.argv.slice(4);
1640
- require('../commands/member').memberCommand(subcommand, ...args);
1666
+ Promise.resolve(require('../commands/member').memberCommand(subcommand, ...args))
1667
+ .then(() => process.exit(0))
1668
+ .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
1641
1669
  } else if (command === 'app') {
1642
1670
  const subcommand = process.argv[3];
1643
1671
  const args = process.argv.slice(4);
@@ -1745,6 +1773,12 @@ if (command === 'init') {
1745
1773
  const subcommand = process.argv[3];
1746
1774
  const args = process.argv.slice(4);
1747
1775
  require('../commands/experiments').experimentsCommand(subcommand, ...args);
1776
+ } else if (command === 'compile') {
1777
+ const subcommand = process.argv[3];
1778
+ const args = process.argv.slice(4);
1779
+ Promise.resolve(require('../commands/compile').compileCommand(subcommand, ...args))
1780
+ .then(() => process.exit(process.exitCode || 0))
1781
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1748
1782
  } else if (command === 'receipt' || command === 'proof' || command === 'openclaw') {
1749
1783
  const subcommand = process.argv[3];
1750
1784
  const args = process.argv.slice(4);
package/commands/align.js CHANGED
@@ -173,20 +173,6 @@ async function walkCloud(token, businessId, workspaceId) {
173
173
  return { files: out, errors };
174
174
  }
175
175
 
176
- /**
177
- * Get cloud file content (for hash computation when /files doesn't return hashes).
178
- */
179
- async function fetchCloudFileHash(token, businessId, workspaceId, filePath) {
180
- const result = await apiRequestJson(
181
- `/business/${businessId}/workspaces/${workspaceId}/file?path=${encodeURIComponent(filePath)}`,
182
- { method: 'GET', token }
183
- );
184
- if (!result.ok) return null;
185
- const content = result.data && result.data.content;
186
- if (typeof content !== 'string') return null;
187
- return hashContent(Buffer.from(content, 'utf-8'));
188
- }
189
-
190
176
  /**
191
177
  * Wake the EC2 computer and wait until it's running.
192
178
  * Returns the endpoint URL or null on timeout.
package/commands/apps.js CHANGED
@@ -1,6 +1,10 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { spawnSync } = require('child_process');
4
+ const { ensureValidCredentials } = require('../utils/auth');
5
+ const { apiRequestJson } = require('../utils/api');
6
+
7
+ const CLOUD_LOAD_COMMANDS = new Set(['load', 'cloud', 'mine']);
4
8
 
5
9
  function findAppsPackRoot(startDir = process.cwd()) {
6
10
  const explicit = process.env.ATRIS_APPS_PACK;
@@ -25,6 +29,7 @@ function appsUsageLines() {
25
29
  '',
26
30
  'Commands:',
27
31
  ' list [--json] List available local apps',
32
+ ' load [--filter kind] [--json] Load owned cloud apps from Atris',
28
33
  ' run <slug> [--lines N] Run an app and print data/latest.md or --json status',
29
34
  ' owner <slug> [--json] Show owner view: launch, usage, learning, next actions',
30
35
  ' status [--json] Show local app health',
@@ -156,7 +161,99 @@ function runPackScript(packRoot, script, args) {
156
161
  process.exit(result.status ?? 0);
157
162
  }
158
163
 
159
- function appsCommand(subcommand, ...rawArgs) {
164
+ function normalizeCloudAppsPayload(data) {
165
+ if (Array.isArray(data)) return data;
166
+ if (Array.isArray(data?.apps)) return data.apps;
167
+ if (Array.isArray(data?.items)) return data.items;
168
+ if (Array.isArray(data?.data)) return data.data;
169
+ return [];
170
+ }
171
+
172
+ function compactCloudApp(app) {
173
+ const slug = app?.slug || app?.id || app?.name || 'unknown';
174
+ return {
175
+ id: app?.id || null,
176
+ name: app?.name || slug,
177
+ slug,
178
+ description: app?.description || null,
179
+ template: app?.template || app?.template_slug || null,
180
+ status: app?.status || app?.health || null,
181
+ last_run: app?.last_run || app?.lastRun || app?.last_run_at || null,
182
+ next_run: app?.next_run || app?.nextRun || app?.next_run_at || null,
183
+ };
184
+ }
185
+
186
+ function printCloudApps(apps, filter) {
187
+ const suffix = filter ? ` (${filter})` : '';
188
+ if (apps.length === 0) {
189
+ console.log(`No cloud apps found${suffix}.`);
190
+ return;
191
+ }
192
+ console.log(`Cloud apps${suffix}:`);
193
+ for (const app of apps) {
194
+ const name = String(app.name || app.slug || 'Untitled app');
195
+ const slug = String(app.slug || app.id || 'unknown');
196
+ const status = app.status ? ` status=${app.status}` : '';
197
+ const template = app.template ? ` template=${app.template}` : '';
198
+ console.log(` ${slug.padEnd(24)} ${name}${status}${template}`);
199
+ }
200
+ }
201
+
202
+ function printAppsLoadHelp() {
203
+ console.log('');
204
+ console.log('Usage: atris apps load [--filter <template|paid|free>] [--json]');
205
+ console.log('');
206
+ console.log('Load owned cloud apps from Atris. Requires `atris login`.');
207
+ console.log('');
208
+ }
209
+
210
+ async function loadCloudApps(rawArgs) {
211
+ const args = [...rawArgs];
212
+ if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
213
+ printAppsLoadHelp();
214
+ return;
215
+ }
216
+ const json = args.includes('--json');
217
+ if (json) args.splice(args.indexOf('--json'), 1);
218
+ let filter = popOption(args, '--filter', null);
219
+ if (!filter && args[0] && !args[0].startsWith('-')) filter = args.shift();
220
+ if (args.length > 0) {
221
+ exitAppsError(`Unknown apps load option: ${args[0]}`, json, { usage: true, code: 2 });
222
+ }
223
+
224
+ const ensured = await ensureValidCredentials(apiRequestJson);
225
+ if (ensured.error || !ensured.credentials?.token) {
226
+ exitAppsError('Not logged in. Run: atris login', json, {
227
+ extra: { login: 'atris login' },
228
+ });
229
+ }
230
+
231
+ const query = filter ? `?filter=${encodeURIComponent(filter)}` : '';
232
+ const result = await apiRequestJson(`/apps${query}`, {
233
+ method: 'GET',
234
+ token: ensured.credentials.token,
235
+ });
236
+ if (!result.ok) {
237
+ exitAppsError(`Failed to load cloud apps: ${result.error || result.status || 'request failed'}`, json, {
238
+ extra: { status: result.status || null },
239
+ });
240
+ }
241
+
242
+ const apps = normalizeCloudAppsPayload(result.data).map(compactCloudApp);
243
+ if (json) {
244
+ console.log(JSON.stringify({
245
+ ok: true,
246
+ source: 'cloud',
247
+ filter: filter || null,
248
+ count: apps.length,
249
+ apps,
250
+ }, null, 2));
251
+ return;
252
+ }
253
+ printCloudApps(apps, filter || null);
254
+ }
255
+
256
+ async function appsCommand(subcommand, ...rawArgs) {
160
257
  const jsonRequested = wantsJson(subcommand, rawArgs);
161
258
  const normalized = normalizeInvocation(subcommand, rawArgs);
162
259
  subcommand = normalized.subcommand;
@@ -166,6 +263,10 @@ function appsCommand(subcommand, ...rawArgs) {
166
263
  printAppsHelp();
167
264
  return;
168
265
  }
266
+ if (CLOUD_LOAD_COMMANDS.has(subcommand)) {
267
+ await loadCloudApps(rawArgs);
268
+ return;
269
+ }
169
270
  const packRoot = findAppsPackRoot();
170
271
  if (!packRoot) {
171
272
  if (jsonRequested) {