atris 3.12.1 → 3.14.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/README.md +39 -14
- package/bin/atris.js +39 -15
- package/commands/business.js +514 -24
- package/commands/computer.js +326 -9
- package/commands/errors.js +155 -0
- package/commands/proof.js +115 -0
- package/commands/pull.js +12 -6
- package/commands/push.js +8 -2
- package/commands/task.js +217 -0
- package/commands/visualize.js +324 -8
- package/lib/task-db.js +288 -0
- package/lib/todo-fallback.js +142 -0
- package/lib/todo.js +99 -184
- package/package.json +2 -2
- package/cli/__pycache__/atris_code.cpython-314.pyc +0 -0
- package/cli/__pycache__/runtime_guard.cpython-312.pyc +0 -0
- package/cli/__pycache__/runtime_guard.cpython-314.pyc +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function readBusinessBinding(cwd = process.cwd()) {
|
|
5
|
+
const bindingPath = path.join(cwd, '.atris', 'business.json');
|
|
6
|
+
if (!fs.existsSync(bindingPath)) return null;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(fs.readFileSync(bindingPath, 'utf8'));
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function slugify(value) {
|
|
15
|
+
return String(value || 'business-workflow')
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
18
|
+
.replace(/^-+|-+$/g, '') || 'business-workflow';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureDir(dir) {
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeJson(filePath, value) {
|
|
26
|
+
ensureDir(path.dirname(filePath));
|
|
27
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function doctor(cwd = process.cwd()) {
|
|
31
|
+
const binding = readBusinessBinding(cwd);
|
|
32
|
+
console.log('Receipt check');
|
|
33
|
+
console.log(`business binding: ${binding ? `${binding.name || binding.slug || binding.business_id} ready` : 'missing'}`);
|
|
34
|
+
console.log(`receipt folder: ${fs.existsSync(path.join(cwd, '.atris', 'receipts')) ? 'ready' : 'missing'}`);
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log('Next: atris receipt init business-workflow');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function init(taskSlug = 'business-workflow', cwd = process.cwd()) {
|
|
40
|
+
const binding = readBusinessBinding(cwd);
|
|
41
|
+
if (!binding) {
|
|
42
|
+
console.error('No business binding found. Run: atris business init <name> --here');
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const slug = slugify(taskSlug);
|
|
48
|
+
const root = path.join(cwd, '.atris');
|
|
49
|
+
const taskPath = path.join(root, 'tasks', `${slug}.json`);
|
|
50
|
+
const receiptsDir = path.join(root, 'receipts');
|
|
51
|
+
|
|
52
|
+
ensureDir(receiptsDir);
|
|
53
|
+
fs.writeFileSync(path.join(receiptsDir, '.gitkeep'), '');
|
|
54
|
+
writeJson(taskPath, {
|
|
55
|
+
schema: 'atris.receipt_task.v1',
|
|
56
|
+
slug,
|
|
57
|
+
goal: 'Run one business-computer task and save what happened.',
|
|
58
|
+
workspace: {
|
|
59
|
+
business_id: binding.business_id || binding.id || null,
|
|
60
|
+
workspace_id: binding.workspace_id || null,
|
|
61
|
+
name: binding.name || null,
|
|
62
|
+
slug: binding.slug || null,
|
|
63
|
+
},
|
|
64
|
+
runtime: {
|
|
65
|
+
proof_command: 'atris computer proof',
|
|
66
|
+
replay_command: 'atris experiments replay endstate',
|
|
67
|
+
},
|
|
68
|
+
verify: [
|
|
69
|
+
'atris computer proof',
|
|
70
|
+
'atris experiments replay endstate',
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
console.log(`Receipt task ready: ${slug}`);
|
|
75
|
+
console.log(`Task: ${path.relative(cwd, taskPath)}`);
|
|
76
|
+
console.log(`Receipts: ${path.relative(cwd, receiptsDir)}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function run(args = []) {
|
|
80
|
+
const dryRun = args.includes('--dry-run');
|
|
81
|
+
console.log('Receipt run');
|
|
82
|
+
console.log('1. atris computer proof');
|
|
83
|
+
console.log('2. atris experiments replay endstate');
|
|
84
|
+
if (dryRun) {
|
|
85
|
+
console.log('Dry run only; no receipts written.');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log('Run those commands, then save the receipt under .atris/receipts/.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function proofCommand(subcommand = 'doctor', ...args) {
|
|
92
|
+
switch (subcommand || 'doctor') {
|
|
93
|
+
case 'doctor':
|
|
94
|
+
return doctor();
|
|
95
|
+
case 'init':
|
|
96
|
+
return init(args[0] || 'business-workflow');
|
|
97
|
+
case 'proof':
|
|
98
|
+
return run(args);
|
|
99
|
+
case 'help':
|
|
100
|
+
case '--help':
|
|
101
|
+
case '-h':
|
|
102
|
+
console.log('Usage: atris receipt [doctor|init <slug>|run --dry-run]');
|
|
103
|
+
return;
|
|
104
|
+
case 'run':
|
|
105
|
+
return run(args);
|
|
106
|
+
default:
|
|
107
|
+
console.error(`Unknown receipt command: ${subcommand}`);
|
|
108
|
+
console.log('Usage: atris receipt [doctor|init <slug>|run --dry-run]');
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
proofCommand,
|
|
115
|
+
};
|
package/commands/pull.js
CHANGED
|
@@ -6,7 +6,7 @@ const { findAllMembers } = require('./member');
|
|
|
6
6
|
const { loadConfig } = require('../utils/config');
|
|
7
7
|
const { getLogPath } = require('../lib/file-ops');
|
|
8
8
|
const { parseJournalSections, mergeSections, reconstructJournal } = require('../lib/journal');
|
|
9
|
-
const { loadBusinesses } = require('./business');
|
|
9
|
+
const { loadBusinesses, businessMatchesSlug } = require('./business');
|
|
10
10
|
const { loadManifest, saveManifest, computeFileHash, buildManifest, computeLocalHashes, threeWayCompare } = require('../lib/manifest');
|
|
11
11
|
const { normalizeWikiOnlyPrefix } = require('../lib/wiki');
|
|
12
12
|
const { emitSyncEvent, startTimer } = require('../lib/sync-telemetry');
|
|
@@ -204,6 +204,7 @@ async function pullBusiness(slug) {
|
|
|
204
204
|
|
|
205
205
|
// Resolve business ID — always refresh from API to avoid stale workspace_id
|
|
206
206
|
let businessId, workspaceId, businessName, resolvedSlug;
|
|
207
|
+
let localSlug = slug;
|
|
207
208
|
const businesses = loadBusinesses();
|
|
208
209
|
|
|
209
210
|
const listResult = await apiRequestJson('/business/', { method: 'GET', token: creds.token });
|
|
@@ -220,7 +221,7 @@ async function pullBusiness(slug) {
|
|
|
220
221
|
}
|
|
221
222
|
} else {
|
|
222
223
|
const match = (listResult.data || []).find(
|
|
223
|
-
b => b
|
|
224
|
+
b => businessMatchesSlug(b, slug, { includeName: true })
|
|
224
225
|
);
|
|
225
226
|
if (!match) {
|
|
226
227
|
console.error(`Business "${slug}" not found.`);
|
|
@@ -230,13 +231,15 @@ async function pullBusiness(slug) {
|
|
|
230
231
|
workspaceId = match.workspace_id;
|
|
231
232
|
businessName = match.name;
|
|
232
233
|
resolvedSlug = match.slug;
|
|
234
|
+
localSlug = businessMatchesSlug(match, slug) ? slug : match.slug;
|
|
233
235
|
|
|
234
236
|
// Update local cache
|
|
235
237
|
businesses[slug] = {
|
|
236
238
|
business_id: businessId,
|
|
237
239
|
workspace_id: workspaceId,
|
|
238
240
|
name: businessName,
|
|
239
|
-
slug:
|
|
241
|
+
slug: localSlug,
|
|
242
|
+
canonical_slug: match.slug,
|
|
240
243
|
added_at: new Date().toISOString(),
|
|
241
244
|
};
|
|
242
245
|
const { saveBusinesses } = require('./business');
|
|
@@ -697,14 +700,17 @@ async function pullBusiness(slug) {
|
|
|
697
700
|
const atrisDir = path.join(outputDir, '.atris');
|
|
698
701
|
fs.mkdirSync(atrisDir, { recursive: true });
|
|
699
702
|
fs.writeFileSync(path.join(atrisDir, 'business.json'), JSON.stringify({
|
|
700
|
-
slug:
|
|
703
|
+
slug: localSlug,
|
|
704
|
+
canonical_slug: resolvedSlug || slug,
|
|
701
705
|
business_id: businessId,
|
|
702
706
|
workspace_id: workspaceId,
|
|
703
707
|
name: businessName,
|
|
704
708
|
}, null, 2));
|
|
705
709
|
|
|
706
|
-
// Wire skills → .claude/skills/ so they work as slash commands
|
|
707
|
-
|
|
710
|
+
// Wire skills → .claude/skills/ so they work as slash commands.
|
|
711
|
+
// Source of truth is atris/skills/ (vendor-neutral, syncs to cloud).
|
|
712
|
+
// .claude/skills/ is a locally-generated adapter Claude Code reads from.
|
|
713
|
+
const skillsDir = path.join(outputDir, 'atris', 'skills');
|
|
708
714
|
const claudeSkillsDir = path.join(outputDir, '.claude', 'skills');
|
|
709
715
|
|
|
710
716
|
if (fs.existsSync(skillsDir)) {
|
package/commands/push.js
CHANGED
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { loadCredentials } = require('../utils/auth');
|
|
5
5
|
const { apiRequestJson } = require('../utils/api');
|
|
6
|
-
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
6
|
+
const { loadBusinesses, saveBusinesses, businessMatchesSlug } = require('./business');
|
|
7
7
|
const { loadManifest, saveManifest, buildManifest, computeLocalHashes } = require('../lib/manifest');
|
|
8
8
|
const { normalizeWikiOnlyPrefix } = require('../lib/wiki');
|
|
9
9
|
const { emitSyncEvent, startTimer } = require('../lib/sync-telemetry');
|
|
@@ -92,7 +92,7 @@ async function pushAtris() {
|
|
|
92
92
|
const businesses = loadBusinesses();
|
|
93
93
|
const listResult = await apiRequestJson('/business/', { method: 'GET', token: creds.token });
|
|
94
94
|
if (listResult.ok) {
|
|
95
|
-
const match = (listResult.data || []).find(b => b
|
|
95
|
+
const match = (listResult.data || []).find(b => businessMatchesSlug(b, slug, { includeName: true }));
|
|
96
96
|
if (!match) { console.error(`Business "${slug}" not found.`); process.exit(1); }
|
|
97
97
|
businessId = match.id;
|
|
98
98
|
workspaceId = match.workspace_id;
|
|
@@ -367,6 +367,12 @@ async function pushAtris() {
|
|
|
367
367
|
|
|
368
368
|
if (!result.ok) {
|
|
369
369
|
if (result.status === 403) {
|
|
370
|
+
const detail = result.errorMessage || result.error || (result.data && result.data.detail) || '';
|
|
371
|
+
if (detail && /plan required|business, max, or enterprise/i.test(detail)) {
|
|
372
|
+
console.error(`\n Access denied: ${detail}`);
|
|
373
|
+
await emit('access_denied', { error_detail: detail });
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
370
376
|
// Permission denied — retry with only team/ and journal/ files
|
|
371
377
|
const allowed = filesToPush.filter(f => f.path.startsWith('/team/') || f.path.startsWith('/journal/'));
|
|
372
378
|
skipped = filesToPush.filter(f => !f.path.startsWith('/team/') && !f.path.startsWith('/journal/'));
|
package/commands/task.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// `atris task` — SQLite-backed task plane. TODO.md stays the human-readable
|
|
2
|
+
// board; this gives agents atomic claims and a compact sync row.
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
11
|
+
|| process.env.USER
|
|
12
|
+
|| os.userInfo().username
|
|
13
|
+
|| 'unknown';
|
|
14
|
+
|
|
15
|
+
let taskDbModule = null;
|
|
16
|
+
|
|
17
|
+
function getTaskDb() {
|
|
18
|
+
if (taskDbModule) return taskDbModule;
|
|
19
|
+
try {
|
|
20
|
+
taskDbModule = require('../lib/task-db');
|
|
21
|
+
return taskDbModule;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
const message = String(e && (e.message || e));
|
|
24
|
+
const missingSqlite = e && (
|
|
25
|
+
e.code === 'ERR_UNKNOWN_BUILTIN_MODULE'
|
|
26
|
+
|| /node:sqlite|No such built-in module/i.test(message)
|
|
27
|
+
);
|
|
28
|
+
if (missingSqlite) {
|
|
29
|
+
console.error('atris task requires Node.js 22+ because it uses built-in node:sqlite.');
|
|
30
|
+
console.error('Use the markdown TODO.md flow on older Node versions.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function help() {
|
|
38
|
+
console.log(`
|
|
39
|
+
atris task — local agent task plane (SQLite, gitignored)
|
|
40
|
+
|
|
41
|
+
atris task add "<title>" [--tag <tag>] Create a task
|
|
42
|
+
atris task list [--all] [--status <s>] List tasks (default: this workspace)
|
|
43
|
+
atris task claim <id> [--as <owner>] Atomic claim
|
|
44
|
+
atris task done <id> [--failed] Mark complete (or failed)
|
|
45
|
+
atris task import <file> One-shot import from TODO.md
|
|
46
|
+
atris task where Print db path + workspace scope
|
|
47
|
+
atris task help This help
|
|
48
|
+
|
|
49
|
+
Env:
|
|
50
|
+
ATRIS_TASKS_DB Override db path (default ~/.atris/tasks.db)
|
|
51
|
+
ATRIS_AGENT_ID Owner id for claim/done (default: $USER)
|
|
52
|
+
`.trim());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function flag(args, name) {
|
|
56
|
+
const i = args.indexOf(name);
|
|
57
|
+
if (i === -1) return null;
|
|
58
|
+
return args[i + 1] || true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hasFlag(args, name) {
|
|
62
|
+
return args.indexOf(name) !== -1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function positional(args) {
|
|
66
|
+
return args.filter((a, i) => {
|
|
67
|
+
if (a.startsWith('--')) return false;
|
|
68
|
+
if (i > 0 && args[i - 1].startsWith('--')) return false;
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cmdAdd(args) {
|
|
74
|
+
const pos = positional(args);
|
|
75
|
+
const title = pos.join(' ').trim();
|
|
76
|
+
if (!title) {
|
|
77
|
+
console.error('atris task add: title required');
|
|
78
|
+
process.exit(2);
|
|
79
|
+
}
|
|
80
|
+
const tag = flag(args, '--tag');
|
|
81
|
+
const taskDb = getTaskDb();
|
|
82
|
+
const db = taskDb.open();
|
|
83
|
+
const ws = taskDb.workspaceRoot();
|
|
84
|
+
const result = taskDb.addTask(db, {
|
|
85
|
+
title,
|
|
86
|
+
tag: typeof tag === 'string' ? tag : null,
|
|
87
|
+
workspaceRoot: ws,
|
|
88
|
+
});
|
|
89
|
+
console.log(`${result.id}\t${title}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function cmdList(args) {
|
|
93
|
+
const all = hasFlag(args, '--all');
|
|
94
|
+
const status = flag(args, '--status');
|
|
95
|
+
const taskDb = getTaskDb();
|
|
96
|
+
const db = taskDb.open();
|
|
97
|
+
const rows = taskDb.listTasks(db, {
|
|
98
|
+
workspaceRoot: all ? null : taskDb.workspaceRoot(),
|
|
99
|
+
status: typeof status === 'string' ? status : null,
|
|
100
|
+
limit: 200,
|
|
101
|
+
});
|
|
102
|
+
if (rows.length === 0) {
|
|
103
|
+
console.log('(no tasks)');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const r of rows) {
|
|
107
|
+
const claim = r.claimed_by ? ` [${r.claimed_by}]` : '';
|
|
108
|
+
const tag = r.tag ? ` #${r.tag}` : '';
|
|
109
|
+
console.log(`${r.status.padEnd(8)} ${r.id}${claim}${tag}\t${r.title}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function cmdClaim(args) {
|
|
114
|
+
const pos = positional(args);
|
|
115
|
+
const id = pos[0];
|
|
116
|
+
if (!id) {
|
|
117
|
+
console.error('atris task claim: id required');
|
|
118
|
+
process.exit(2);
|
|
119
|
+
}
|
|
120
|
+
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
121
|
+
const taskDb = getTaskDb();
|
|
122
|
+
const db = taskDb.open();
|
|
123
|
+
const result = taskDb.claimTask(db, { id, claimedBy: String(owner) });
|
|
124
|
+
if (result.claimed) {
|
|
125
|
+
console.log(`claimed ${id} as ${owner}`);
|
|
126
|
+
} else {
|
|
127
|
+
console.error(`claim failed: ${result.reason}${result.claimed_by ? ` (held by ${result.claimed_by})` : ''}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function cmdDone(args) {
|
|
133
|
+
const pos = positional(args);
|
|
134
|
+
const id = pos[0];
|
|
135
|
+
if (!id) {
|
|
136
|
+
console.error('atris task done: id required');
|
|
137
|
+
process.exit(2);
|
|
138
|
+
}
|
|
139
|
+
const failed = hasFlag(args, '--failed');
|
|
140
|
+
const taskDb = getTaskDb();
|
|
141
|
+
const db = taskDb.open();
|
|
142
|
+
const result = taskDb.doneTask(db, { id, status: failed ? 'failed' : 'done' });
|
|
143
|
+
if (result.updated) {
|
|
144
|
+
console.log(`${failed ? 'failed' : 'done'} ${id}`);
|
|
145
|
+
} else {
|
|
146
|
+
console.error(`done failed: ${id} not in open|claimed`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function cmdImport(args) {
|
|
152
|
+
const pos = positional(args);
|
|
153
|
+
const target = pos[0] || 'atris/TODO.md';
|
|
154
|
+
const filePath = path.resolve(target);
|
|
155
|
+
if (!fs.existsSync(filePath)) {
|
|
156
|
+
console.error(`atris task import: file not found: ${filePath}`);
|
|
157
|
+
process.exit(2);
|
|
158
|
+
}
|
|
159
|
+
const { parseTodoFile } = require('../lib/todo-fallback');
|
|
160
|
+
const parsed = parseTodoFile(filePath);
|
|
161
|
+
const taskDb = getTaskDb();
|
|
162
|
+
const db = taskDb.open();
|
|
163
|
+
const ws = taskDb.workspaceRoot();
|
|
164
|
+
const all = [
|
|
165
|
+
...parsed.backlog.map(t => ({ ...t, importStatus: 'open' })),
|
|
166
|
+
...parsed.inProgress.map(t => ({ ...t, importStatus: 'claimed' })),
|
|
167
|
+
];
|
|
168
|
+
let inserted = 0;
|
|
169
|
+
let skipped = 0;
|
|
170
|
+
for (const t of all) {
|
|
171
|
+
if (!t.title) continue;
|
|
172
|
+
const sk = taskDb.sourceKey(filePath, t.title);
|
|
173
|
+
const result = taskDb.addTask(db, {
|
|
174
|
+
title: t.title,
|
|
175
|
+
tag: t.tag || null,
|
|
176
|
+
workspaceRoot: ws,
|
|
177
|
+
sourceKey: sk,
|
|
178
|
+
status: t.importStatus,
|
|
179
|
+
claimedBy: t.claimed || null,
|
|
180
|
+
metadata: { todo_id: t.id, claimed: t.claimed, stage: t.stage, verify: t.verify },
|
|
181
|
+
});
|
|
182
|
+
if (result.inserted) inserted++; else skipped++;
|
|
183
|
+
}
|
|
184
|
+
console.log(`imported ${inserted} new, skipped ${skipped} (already imported), source=${filePath}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function cmdWhere() {
|
|
188
|
+
const taskDb = getTaskDb();
|
|
189
|
+
console.log(`db: ${taskDb.getDbPath()}`);
|
|
190
|
+
console.log(`workspace: ${taskDb.workspaceRoot()}`);
|
|
191
|
+
console.log(`owner: ${DEFAULT_OWNER}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function run(args) {
|
|
195
|
+
const sub = (args && args[0]) || 'help';
|
|
196
|
+
const rest = (args || []).slice(1);
|
|
197
|
+
switch (sub) {
|
|
198
|
+
case 'add': return cmdAdd(rest);
|
|
199
|
+
case 'list': return cmdList(rest);
|
|
200
|
+
case 'ls': return cmdList(rest);
|
|
201
|
+
case 'claim': return cmdClaim(rest);
|
|
202
|
+
case 'done': return cmdDone(rest);
|
|
203
|
+
case 'fail': return cmdDone([...rest, '--failed']);
|
|
204
|
+
case 'import': return cmdImport(rest);
|
|
205
|
+
case 'where': return cmdWhere();
|
|
206
|
+
case 'help':
|
|
207
|
+
case '--help':
|
|
208
|
+
case '-h':
|
|
209
|
+
return help();
|
|
210
|
+
default:
|
|
211
|
+
console.error(`atris task: unknown subcommand "${sub}"`);
|
|
212
|
+
help();
|
|
213
|
+
process.exit(2);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { run };
|