atris 3.58.6 → 3.58.8
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/GETTING_STARTED.md +2 -2
- package/PERSONA.md +11 -11
- package/README.md +9 -1
- package/atris/GEMINI.md +7 -6
- package/atris/PERSONA.md +4 -4
- package/atris/features/README.md +77 -6
- package/atris/policies/engineering-principles.md +129 -0
- package/atris/policies/genesis.md +112 -0
- package/atris/policies/product-design-principles.md +100 -0
- package/atris/skills/README.md +44 -15
- package/atris/skills/aeo/SKILL.md +10 -10
- package/atris/skills/atris/SKILL.md +1 -1
- package/atris/skills/design/SKILL.md +3 -1
- package/atris/skills/engines/SKILL.md +42 -50
- package/atris/skills/meta/SKILL.md +4 -4
- package/atris/skills/youtube/SKILL.md +11 -11
- package/bin/atris.js +60 -8
- package/commands/aeo.js +9 -12
- package/commands/analytics.js +8 -11
- package/commands/auth.js +8 -2
- package/commands/autopilot.js +4 -3
- package/commands/avail.js +1 -1
- package/commands/brain.js +4 -1
- package/commands/business.js +13 -8
- package/commands/close.js +2 -1
- package/commands/computer.js +15 -10
- package/commands/context-sync.js +3 -3
- package/commands/design.js +362 -0
- package/commands/doc-health.js +377 -0
- package/commands/drive.js +32 -0
- package/commands/experiments.js +13 -4
- package/commands/improve.js +68 -2
- package/commands/integrations.js +2 -5
- package/commands/land.js +144 -4
- package/commands/learn.js +7 -2
- package/commands/member.js +239 -58
- package/commands/mission.js +94 -18
- package/commands/pack.js +2 -2
- package/commands/probe.js +11 -5
- package/commands/pull.js +1 -1
- package/commands/pulse.js +38 -0
- package/commands/push.js +1 -1
- package/commands/rainmaker.js +3 -16
- package/commands/rsi.js +156 -0
- package/commands/spaceship.js +1 -1
- package/commands/stream.js +27 -2
- package/commands/study.js +15 -9
- package/commands/sync.js +1 -1
- package/commands/task.js +54 -3
- package/commands/workflow.js +17 -7
- package/commands/youtube.js +76 -8
- package/lib/apply-gate.js +22 -4
- package/lib/codex-flight.js +28 -5
- package/lib/daily-log.js +88 -0
- package/lib/design-api.js +130 -0
- package/lib/engine-ask.js +1 -1
- package/lib/first-minute.js +1 -6
- package/lib/fleet.js +4 -1
- package/lib/improve-vitals-html.js +3 -2
- package/lib/known-commands.js +3 -3
- package/lib/lesson-contradiction.js +2 -2
- package/lib/map-refs.js +183 -0
- package/lib/member-context.js +42 -0
- package/lib/mission-room.js +4 -4
- package/lib/noninteractive.js +15 -4
- package/lib/policy-lessons.js +1 -1
- package/lib/receipt-block.js +137 -28
- package/lib/rsi-record.js +335 -0
- package/lib/runner-command.js +1 -1
- package/lib/scorecard.js +4 -4
- package/lib/sleep-sync.js +11 -0
- package/lib/state-detection.js +36 -17
- package/lib/task-db.js +74 -53
- package/lib/task-list-keeper.js +192 -0
- package/lib/todo-fallback.js +10 -4
- package/lib/todo.js +22 -10
- package/lib/wish-delegate.js +19 -5
- package/mcp/atris-mcp/index.mjs +227 -0
- package/package.json +4 -2
- package/scripts/det/det.js +2 -2
- package/scripts/det/pr-description.js +2 -2
- package/scripts/det/test.js +2 -2
- package/scripts/det/ytnotes +43 -8
- package/utils/api.js +1 -1
- package/utils/auth.js +79 -14
- package/utils/backend-root.js +31 -0
- package/utils/config.js +5 -4
- package/utils/owner-identity.js +61 -0
- package/atris/policies/LESSONS.md +0 -13
- package/atris/skills/autopilot/atris-autopilot.md +0 -71
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* atris design, brand design-system commands against api.atris.ai.
|
|
5
|
+
*
|
|
6
|
+
* atris design extract <url> [--json] [--sections colors,typography]
|
|
7
|
+
* atris design check <url> --against <brand-url> [--json]
|
|
8
|
+
* atris design search "<words>" [--limit n] [--json]
|
|
9
|
+
*
|
|
10
|
+
* Auth: developer key as `Authorization: Bearer atris_...`, resolved by
|
|
11
|
+
* lib/design-api.js (ATRIS_API_KEY, then the atris login token, then
|
|
12
|
+
* ~/.atris/design-api-key).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
resolveDesignKey,
|
|
17
|
+
designRequest,
|
|
18
|
+
pollDesignJob,
|
|
19
|
+
billingOf,
|
|
20
|
+
creditLine,
|
|
21
|
+
} = require('../lib/design-api');
|
|
22
|
+
|
|
23
|
+
const NO_KEY = 'no api key found. set ATRIS_API_KEY or run: atris login (or save a key in ~/.atris/design-api-key)';
|
|
24
|
+
|
|
25
|
+
function showDesignHelp() {
|
|
26
|
+
console.log('usage: atris design extract <url> [--json] [--sections colors,typography]');
|
|
27
|
+
console.log(' atris design check <url> --against <brand-url> [--json]');
|
|
28
|
+
console.log(' atris design search "<words>" [--limit n] [--json]');
|
|
29
|
+
console.log('pull a site\'s design system, score a page against a brand, or search extracted brands.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function wantsHelp(args) {
|
|
33
|
+
return args.includes('--help') || args.includes('-h') || args[0] === 'help';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readFlag(args, name) {
|
|
37
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
38
|
+
const a = String(args[i]);
|
|
39
|
+
if (a === name) {
|
|
40
|
+
const v = args[i + 1];
|
|
41
|
+
if (v === undefined || String(v).startsWith('--')) return { error: `${name} needs a value` };
|
|
42
|
+
args.splice(i, 2);
|
|
43
|
+
return { value: String(v) };
|
|
44
|
+
}
|
|
45
|
+
if (a.startsWith(`${name}=`)) {
|
|
46
|
+
args.splice(i, 1);
|
|
47
|
+
return { value: a.slice(name.length + 1) };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function positionals(args) {
|
|
54
|
+
return args.filter((a) => !String(a).startsWith('--'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function unknownFlags(args, allowed) {
|
|
58
|
+
return args.filter((a) => String(a).startsWith('--') && !allowed.includes(a));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeUrl(url) {
|
|
62
|
+
return /^https?:\/\//i.test(url) ? url : `https://${url}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isTerminal(job) {
|
|
66
|
+
const s = String(job && job.status || '').toLowerCase();
|
|
67
|
+
return s === 'completed' || s === 'failed' || s === 'error' || s === 'succeeded';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// One-line spinner on TTY only; off TTY stays silent so --json and pipes stay clean.
|
|
71
|
+
function makeSpinner(label, io) {
|
|
72
|
+
const write = io.write;
|
|
73
|
+
if (!io.tty || typeof write !== 'function') return { stop() {} };
|
|
74
|
+
const start = Date.now();
|
|
75
|
+
const frames = ['-', '\\', '|', '/'];
|
|
76
|
+
let n = 0;
|
|
77
|
+
const timer = setInterval(() => {
|
|
78
|
+
const secs = Math.floor((Date.now() - start) / 1000);
|
|
79
|
+
write(`\r ${frames[n++ % frames.length]} ${label} ${secs}s`);
|
|
80
|
+
}, 250);
|
|
81
|
+
if (timer.unref) timer.unref();
|
|
82
|
+
return {
|
|
83
|
+
stop() {
|
|
84
|
+
clearInterval(timer);
|
|
85
|
+
write('\r\x1b[2K');
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function uniqueHexes(colors = {}, palette = []) {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const key of ['primary', 'secondary', 'accent']) {
|
|
93
|
+
const hex = colors[key];
|
|
94
|
+
if (hex && !out.includes(hex)) out.push(hex);
|
|
95
|
+
}
|
|
96
|
+
for (const p of Array.isArray(palette) ? palette : []) {
|
|
97
|
+
const hex = p && typeof p === 'object' ? p.hex : p;
|
|
98
|
+
if (hex && !out.includes(hex)) out.push(hex);
|
|
99
|
+
if (out.length >= 6) break;
|
|
100
|
+
}
|
|
101
|
+
return out.slice(0, 6);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function fontFamily(value) {
|
|
105
|
+
if (!value) return null;
|
|
106
|
+
if (typeof value === 'string') return value;
|
|
107
|
+
if (typeof value === 'object') return value.family || null;
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Short readable card for an extraction job.
|
|
112
|
+
function extractionCard(job = {}) {
|
|
113
|
+
const ds = job.result && job.result.design_system ? job.result.design_system : {};
|
|
114
|
+
const profile = ds.profile || {};
|
|
115
|
+
const colors = ds.colors || {};
|
|
116
|
+
const typo = ds.typography || {};
|
|
117
|
+
const lines = [];
|
|
118
|
+
lines.push('');
|
|
119
|
+
lines.push(` ${profile.brand_name || job.source_url || 'unknown brand'}`);
|
|
120
|
+
if (job.source_url) lines.push(` ${job.source_url}`);
|
|
121
|
+
lines.push('');
|
|
122
|
+
const hexes = uniqueHexes(colors, colors.palette);
|
|
123
|
+
if (hexes.length) lines.push(` colors ${hexes.join(' ')}`);
|
|
124
|
+
const heading = fontFamily(typo.heading);
|
|
125
|
+
const body = fontFamily(typo.body);
|
|
126
|
+
if (heading) lines.push(` heading ${heading}`);
|
|
127
|
+
if (body) lines.push(` body ${body}`);
|
|
128
|
+
if (profile.one_line_positioning) lines.push(` line ${profile.one_line_positioning}`);
|
|
129
|
+
lines.push('');
|
|
130
|
+
lines.push(` ${creditLine(job)}`);
|
|
131
|
+
lines.push('');
|
|
132
|
+
return lines.join('\n');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Short readable card for an adherence job.
|
|
136
|
+
function adherenceCard(job = {}) {
|
|
137
|
+
const result = job.result && typeof job.result === 'object' ? job.result : {};
|
|
138
|
+
const lines = [];
|
|
139
|
+
lines.push('');
|
|
140
|
+
const score = Number(result.score);
|
|
141
|
+
lines.push(` score ${Number.isFinite(score) ? score.toFixed(2) : '?'}`);
|
|
142
|
+
if (job.source_url) lines.push(` source ${job.source_url}`);
|
|
143
|
+
if (job.reference_url) lines.push(` against ${job.reference_url}`);
|
|
144
|
+
lines.push('');
|
|
145
|
+
for (const key of ['fixes', 'recommendations']) {
|
|
146
|
+
const items = Array.isArray(result[key]) ? result[key] : [];
|
|
147
|
+
if (!items.length) continue;
|
|
148
|
+
lines.push(` ${key}:`);
|
|
149
|
+
for (const item of items.slice(0, 5)) {
|
|
150
|
+
const text = typeof item === 'string' ? item : (item && (item.title || item.detail || item.message)) || JSON.stringify(item);
|
|
151
|
+
lines.push(` - ${String(text).replace(/\s+/g, ' ').trim()}`);
|
|
152
|
+
}
|
|
153
|
+
lines.push('');
|
|
154
|
+
}
|
|
155
|
+
lines.push(` ${creditLine(job)}`);
|
|
156
|
+
lines.push('');
|
|
157
|
+
return lines.join('\n');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Short readable card for a search response.
|
|
161
|
+
function searchCard(data = {}, query = '') {
|
|
162
|
+
const results = Array.isArray(data.results) ? data.results : [];
|
|
163
|
+
const lines = [];
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push(` ${results.length} result${results.length === 1 ? '' : 's'} for "${query}"`);
|
|
166
|
+
lines.push('');
|
|
167
|
+
for (const r of results) {
|
|
168
|
+
const name = r.brand_name || r.source_url || 'unknown';
|
|
169
|
+
lines.push(` ${name}${r.source_url ? ` ${r.source_url}` : ''}`);
|
|
170
|
+
const palette = Array.isArray(r.palette) ? r.palette.slice(0, 6).join(' ') : '';
|
|
171
|
+
if (palette) lines.push(` ${palette}`);
|
|
172
|
+
}
|
|
173
|
+
lines.push('');
|
|
174
|
+
lines.push(` ${creditLine(data)}`);
|
|
175
|
+
lines.push('');
|
|
176
|
+
return lines.join('\n');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function designExtract(args, ctx) {
|
|
180
|
+
const json = args.includes('--json');
|
|
181
|
+
const sections = readFlag(args, '--sections');
|
|
182
|
+
if (sections.error) { ctx.err(sections.error); return 1; }
|
|
183
|
+
const bad = unknownFlags(args, ['--json']);
|
|
184
|
+
if (bad.length) { ctx.err(`unknown flag for design extract: ${bad.join(' ')}`); return 1; }
|
|
185
|
+
const url = positionals(args)[0];
|
|
186
|
+
if (!url) { ctx.err('usage: atris design extract <url> [--json] [--sections colors,typography]'); return 1; }
|
|
187
|
+
const target = normalizeUrl(url);
|
|
188
|
+
|
|
189
|
+
const body = { url: target };
|
|
190
|
+
if (sections.value) body.sections = sections.value;
|
|
191
|
+
const first = await ctx.request('/design/extractions', { method: 'POST', body, key: ctx.key });
|
|
192
|
+
if (!first.ok) {
|
|
193
|
+
ctx.err(`design extract failed (${first.status}): ${first.error}`);
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
let job = first.data || {};
|
|
197
|
+
const pollMs = ctx.pollMs;
|
|
198
|
+
if (!isTerminal(job) && job.id) {
|
|
199
|
+
const qs = sections.value ? `?sections=${encodeURIComponent(sections.value)}` : '';
|
|
200
|
+
const spinner = makeSpinner(`extracting ${target}`, ctx.io);
|
|
201
|
+
let failures = 0;
|
|
202
|
+
const polled = await pollDesignJob(async () => {
|
|
203
|
+
const res = await ctx.request(`/design/extractions/${job.id}${qs}`, { key: ctx.key });
|
|
204
|
+
if (!res.ok) {
|
|
205
|
+
failures += 1;
|
|
206
|
+
if (failures >= 3) return { status: 'failed', error: res.error };
|
|
207
|
+
return { status: 'polling' };
|
|
208
|
+
}
|
|
209
|
+
failures = 0;
|
|
210
|
+
return res.data;
|
|
211
|
+
}, { intervalMs: pollMs, timeoutMs: ctx.maxWaitMs, sleep: ctx.sleep });
|
|
212
|
+
spinner.stop();
|
|
213
|
+
if (polled.timedOut) {
|
|
214
|
+
ctx.err(`still running after ${Math.round(ctx.maxWaitMs / 1000)}s. job id: ${job.id}`);
|
|
215
|
+
return 1;
|
|
216
|
+
}
|
|
217
|
+
job = polled.job || job;
|
|
218
|
+
}
|
|
219
|
+
if (json) { ctx.out(JSON.stringify(job, null, 2)); return job.status === 'completed' ? 0 : 1; }
|
|
220
|
+
if (!isTerminal(job) || job.status !== 'completed') {
|
|
221
|
+
ctx.err(`extraction did not finish: ${job.error || job.status || 'unknown'}`);
|
|
222
|
+
return 1;
|
|
223
|
+
}
|
|
224
|
+
ctx.out(extractionCard(job));
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function designCheck(args, ctx) {
|
|
229
|
+
const json = args.includes('--json');
|
|
230
|
+
const against = readFlag(args, '--against');
|
|
231
|
+
if (against.error) { ctx.err(against.error); return 1; }
|
|
232
|
+
const bad = unknownFlags(args, ['--json']);
|
|
233
|
+
if (bad.length) { ctx.err(`unknown flag for design check: ${bad.join(' ')}`); return 1; }
|
|
234
|
+
const url = positionals(args)[0];
|
|
235
|
+
if (!url || !against.value) {
|
|
236
|
+
ctx.err('usage: atris design check <url> --against <brand-url> [--json]');
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
const source = normalizeUrl(url);
|
|
240
|
+
const reference = normalizeUrl(against.value);
|
|
241
|
+
|
|
242
|
+
const first = await ctx.request('/design/adherence', {
|
|
243
|
+
method: 'POST',
|
|
244
|
+
body: { source_url: source, reference_url: reference },
|
|
245
|
+
key: ctx.key,
|
|
246
|
+
});
|
|
247
|
+
if (!first.ok) {
|
|
248
|
+
ctx.err(`design check failed (${first.status}): ${first.error}`);
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
251
|
+
let job = first.data || {};
|
|
252
|
+
if (!isTerminal(job) && job.id) {
|
|
253
|
+
const spinner = makeSpinner(`checking ${source}`, ctx.io);
|
|
254
|
+
let failures = 0;
|
|
255
|
+
const polled = await pollDesignJob(async () => {
|
|
256
|
+
const res = await ctx.request(`/design/adherence/${job.id}`, { key: ctx.key });
|
|
257
|
+
if (!res.ok) {
|
|
258
|
+
failures += 1;
|
|
259
|
+
if (failures >= 3) return { status: 'failed', error: res.error };
|
|
260
|
+
return { status: 'polling' };
|
|
261
|
+
}
|
|
262
|
+
failures = 0;
|
|
263
|
+
return res.data;
|
|
264
|
+
}, { intervalMs: ctx.pollMs, timeoutMs: ctx.maxWaitMs, sleep: ctx.sleep });
|
|
265
|
+
spinner.stop();
|
|
266
|
+
if (polled.timedOut) {
|
|
267
|
+
ctx.err(`still running after ${Math.round(ctx.maxWaitMs / 1000)}s. job id: ${job.id}`);
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
job = polled.job || job;
|
|
271
|
+
}
|
|
272
|
+
if (json) { ctx.out(JSON.stringify(job, null, 2)); return job.status === 'completed' ? 0 : 1; }
|
|
273
|
+
if (job.status !== 'completed') {
|
|
274
|
+
ctx.err(`check did not finish: ${job.error || job.status || 'unknown'}`);
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
ctx.out(adherenceCard(job));
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function designSearch(args, ctx) {
|
|
282
|
+
const json = args.includes('--json');
|
|
283
|
+
const limitFlag = readFlag(args, '--limit');
|
|
284
|
+
if (limitFlag.error) { ctx.err(limitFlag.error); return 1; }
|
|
285
|
+
const bad = unknownFlags(args, ['--json']);
|
|
286
|
+
if (bad.length) { ctx.err(`unknown flag for design search: ${bad.join(' ')}`); return 1; }
|
|
287
|
+
const query = positionals(args).join(' ').trim();
|
|
288
|
+
if (!query) { ctx.err('usage: atris design search "<words>" [--limit n] [--json]'); return 1; }
|
|
289
|
+
let limit;
|
|
290
|
+
if (limitFlag.value != null) {
|
|
291
|
+
limit = parseInt(limitFlag.value, 10);
|
|
292
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
293
|
+
ctx.err(`invalid --limit value: "${limitFlag.value}". expected a positive integer.`);
|
|
294
|
+
return 1;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const body = { query };
|
|
299
|
+
if (limit != null) body.limit = limit;
|
|
300
|
+
const res = await ctx.request('/design/search', { method: 'POST', body, key: ctx.key });
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
ctx.err(`design search failed (${res.status}): ${res.error}`);
|
|
303
|
+
return 1;
|
|
304
|
+
}
|
|
305
|
+
const data = res.data || {};
|
|
306
|
+
if (json) { ctx.out(JSON.stringify(data, null, 2)); return 0; }
|
|
307
|
+
ctx.out(searchCard(data, query));
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function run(args = [], deps = {}) {
|
|
312
|
+
const rest = args.slice();
|
|
313
|
+
if (rest.length === 0 || wantsHelp(rest)) {
|
|
314
|
+
showDesignHelp();
|
|
315
|
+
return rest.length === 0 ? 1 : 0;
|
|
316
|
+
}
|
|
317
|
+
const sub = rest.shift();
|
|
318
|
+
|
|
319
|
+
const io = deps.io || {
|
|
320
|
+
out: (s) => process.stdout.write(`${s}\n`),
|
|
321
|
+
err: (s) => process.stderr.write(`${s}\n`),
|
|
322
|
+
write: (s) => process.stdout.write(s),
|
|
323
|
+
tty: Boolean(process.stdout.isTTY),
|
|
324
|
+
};
|
|
325
|
+
const ctx = {
|
|
326
|
+
out: deps.out || io.out,
|
|
327
|
+
err: deps.err || io.err,
|
|
328
|
+
io,
|
|
329
|
+
key: deps.key !== undefined ? deps.key : resolveDesignKey(process.env, deps),
|
|
330
|
+
request: deps.request || designRequest,
|
|
331
|
+
sleep: deps.sleep,
|
|
332
|
+
pollMs: deps.pollMs != null
|
|
333
|
+
? deps.pollMs
|
|
334
|
+
: Number(process.env.ATRIS_DESIGN_POLL_MS) || undefined,
|
|
335
|
+
maxWaitMs: deps.maxWaitMs != null
|
|
336
|
+
? deps.maxWaitMs
|
|
337
|
+
: Number(process.env.ATRIS_DESIGN_TIMEOUT_MS) || 3 * 60 * 1000,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
if (!['extract', 'check', 'search'].includes(sub)) {
|
|
341
|
+
ctx.err(`unknown design subcommand: ${sub}`);
|
|
342
|
+
showDesignHelp();
|
|
343
|
+
return 1;
|
|
344
|
+
}
|
|
345
|
+
if (!ctx.key) {
|
|
346
|
+
ctx.err(NO_KEY);
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
if (sub === 'extract') return await designExtract(rest, ctx);
|
|
352
|
+
if (sub === 'check') return await designCheck(rest, ctx);
|
|
353
|
+
return await designSearch(rest, ctx);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
ctx.err(`design ${sub} failed: ${(error && error.message) || error}`);
|
|
356
|
+
return 1;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
module.exports = {
|
|
361
|
+
run,
|
|
362
|
+
};
|