atris 3.38.0 → 3.40.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 +25 -6
- package/atris/PERSONA.md +8 -4
- package/atris.md +7 -0
- package/ax +2 -1
- package/bin/atris.js +30 -5
- package/commands/agent-spawn.js +13 -11
- package/commands/autoland.js +28 -77
- package/commands/bench.js +10 -12
- package/commands/business.js +345 -0
- package/commands/chat-scan.js +5 -7
- package/commands/codex-goal.js +8 -10
- package/commands/console.js +19 -3
- package/commands/decide.js +166 -0
- package/commands/deck.js +1 -4
- package/commands/drill.js +14 -24
- package/commands/engine.js +196 -8
- package/commands/gm.js +8 -6
- package/commands/harvest.js +1 -4
- package/commands/init.js +23 -3
- package/commands/land.js +8 -14
- package/commands/launchpad.js +1 -14
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/member.js +558 -574
- package/commands/mission.js +456 -237
- package/commands/pack.js +2746 -164
- package/commands/play.js +6 -4
- package/commands/probe.js +2 -2
- package/commands/pulse.js +15 -16
- package/commands/release.js +10 -9
- package/commands/router.js +5 -4
- package/commands/site-deploy.js +870 -0
- package/commands/site.js +11 -2
- package/commands/slop.js +14 -2
- package/commands/stream.js +4 -18
- package/commands/task.js +880 -558
- package/commands/taste.js +101 -0
- package/commands/team.js +83 -3
- package/commands/vercel.js +4 -2
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -22
- package/commands/wiki.js +1 -4
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +1 -14
- package/commands/xp.js +27 -24
- package/lib/accept-verify-gate.js +5 -1
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +116 -1
- package/lib/autoland.js +66 -0
- package/lib/bench/runner.js +19 -1
- package/lib/context-gatherer.js +7 -1
- package/lib/engine-registry.js +141 -20
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +65 -15
- package/lib/git-spawn.js +15 -0
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +0 -2
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +4 -1
- package/lib/official-cli-integration.js +47 -2
- package/lib/orb-context.js +8 -1
- package/lib/pack-capabilities.js +685 -0
- package/lib/router-brain.js +51 -1
- package/lib/runner-command.js +0 -6
- package/lib/self-drive.js +44 -13
- package/lib/task-db.js +137 -3
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +66 -0
- package/lib/wish-audit.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/zip.js +95 -7
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/commands/business.js
CHANGED
|
@@ -2028,6 +2028,328 @@ function activityBar(daysSinceActive, width = 10) {
|
|
|
2028
2028
|
return '\u2501'.repeat(filled) + '\u2591'.repeat(width - filled);
|
|
2029
2029
|
}
|
|
2030
2030
|
|
|
2031
|
+
function asRecord(value) {
|
|
2032
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function firstString(...values) {
|
|
2036
|
+
for (const value of values) {
|
|
2037
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
2038
|
+
}
|
|
2039
|
+
return '';
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
function firstNumber(...values) {
|
|
2043
|
+
for (const value of values) {
|
|
2044
|
+
const parsed = typeof value === 'number'
|
|
2045
|
+
? value
|
|
2046
|
+
: (typeof value === 'string' && value.trim() ? Number(value) : Number.NaN);
|
|
2047
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
2048
|
+
}
|
|
2049
|
+
return 0;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function firstBoolean(...values) {
|
|
2053
|
+
for (const value of values) {
|
|
2054
|
+
if (typeof value === 'boolean') return value;
|
|
2055
|
+
if (value === 1 || value === '1' || value === 'true') return true;
|
|
2056
|
+
if (value === 0 || value === '0' || value === 'false') return false;
|
|
2057
|
+
}
|
|
2058
|
+
return false;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
function normalizeOrdersPayload(value) {
|
|
2062
|
+
const root = asRecord(value);
|
|
2063
|
+
const body = asRecord(root.data);
|
|
2064
|
+
const source = Object.keys(body).length > 0 ? body : root;
|
|
2065
|
+
const summary = asRecord(source.summary);
|
|
2066
|
+
const rawOrders = Array.isArray(value)
|
|
2067
|
+
? value
|
|
2068
|
+
: (Array.isArray(source.orders) ? source.orders : (Array.isArray(root.orders) ? root.orders : []));
|
|
2069
|
+
const orders = rawOrders.map((rawOrder, index) => {
|
|
2070
|
+
const order = asRecord(rawOrder);
|
|
2071
|
+
const product = asRecord(order.product);
|
|
2072
|
+
const item = asRecord(order.item);
|
|
2073
|
+
const buyer = asRecord(order.buyer);
|
|
2074
|
+
const customer = asRecord(order.customer);
|
|
2075
|
+
const metadata = asRecord(order.metadata);
|
|
2076
|
+
const firstItem = Array.isArray(order.items) ? asRecord(order.items[0]) : {};
|
|
2077
|
+
const status = firstString(order.status, order.payment_status, order.order_type).toLowerCase();
|
|
2078
|
+
const createdAt = firstString(
|
|
2079
|
+
order.created_at,
|
|
2080
|
+
order.createdAt,
|
|
2081
|
+
order.order_date,
|
|
2082
|
+
order.date,
|
|
2083
|
+
order.paid_at,
|
|
2084
|
+
) || null;
|
|
2085
|
+
|
|
2086
|
+
return {
|
|
2087
|
+
id: firstString(order.id, order.order_id, order.reference) || `order-${index + 1}`,
|
|
2088
|
+
reference: firstString(order.order_ref, order.reference, order.order_number, order.id) || `order ${index + 1}`,
|
|
2089
|
+
product: firstString(
|
|
2090
|
+
order.product_name,
|
|
2091
|
+
order.product_title,
|
|
2092
|
+
typeof order.product === 'string' ? order.product : null,
|
|
2093
|
+
product.name,
|
|
2094
|
+
product.title,
|
|
2095
|
+
item.name,
|
|
2096
|
+
firstItem.name,
|
|
2097
|
+
firstItem.product_name,
|
|
2098
|
+
metadata.product_name,
|
|
2099
|
+
) || 'product',
|
|
2100
|
+
quantity: Math.max(1, Math.round(firstNumber(order.quantity, order.qty, firstItem.quantity, 1))),
|
|
2101
|
+
buyer: firstString(
|
|
2102
|
+
order.buyer_name,
|
|
2103
|
+
order.customer_name,
|
|
2104
|
+
buyer.name,
|
|
2105
|
+
customer.name,
|
|
2106
|
+
order.buyer_email,
|
|
2107
|
+
order.customer_email,
|
|
2108
|
+
buyer.email,
|
|
2109
|
+
customer.email,
|
|
2110
|
+
) || 'guest',
|
|
2111
|
+
status: status || 'unknown',
|
|
2112
|
+
isPreorder: firstBoolean(order.is_preorder, order.preorder)
|
|
2113
|
+
|| status.replace(/[_-]/g, '').includes('preorder')
|
|
2114
|
+
|| status === 'pending',
|
|
2115
|
+
amountCents: firstNumber(order.amount_cents, order.price_cents, order.total_cents, firstItem.amount_cents),
|
|
2116
|
+
createdAt,
|
|
2117
|
+
};
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
orders.sort((a, b) => {
|
|
2121
|
+
const aTime = a.createdAt ? Date.parse(a.createdAt) : 0;
|
|
2122
|
+
const bTime = b.createdAt ? Date.parse(b.createdAt) : 0;
|
|
2123
|
+
return (Number.isFinite(bTime) ? bTime : 0) - (Number.isFinite(aTime) ? aTime : 0);
|
|
2124
|
+
});
|
|
2125
|
+
|
|
2126
|
+
return {
|
|
2127
|
+
orders,
|
|
2128
|
+
revenueCents: firstNumber(summary.revenue_cents, source.revenue_cents, root.revenue_cents),
|
|
2129
|
+
paidOrders: Math.max(0, Math.round(firstNumber(summary.paid_orders, source.paid_orders, root.paid_orders))),
|
|
2130
|
+
pendingRevenueCents: firstNumber(
|
|
2131
|
+
summary.pending_revenue_cents,
|
|
2132
|
+
source.pending_revenue_cents,
|
|
2133
|
+
root.pending_revenue_cents,
|
|
2134
|
+
),
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
function readStorefront(business) {
|
|
2139
|
+
const config = asRecord(asRecord(business).config);
|
|
2140
|
+
const storefront = asRecord(config.storefront);
|
|
2141
|
+
return {
|
|
2142
|
+
enabled: storefront.enabled === true,
|
|
2143
|
+
products: Array.isArray(storefront.products) ? storefront.products.filter(product => product && typeof product === 'object') : [],
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
function readWalletNet(business) {
|
|
2148
|
+
const config = asRecord(asRecord(business).config);
|
|
2149
|
+
for (const key of ['wallet_balance', 'credits_balance', 'credits', 'wallet_credits']) {
|
|
2150
|
+
const value = config[key];
|
|
2151
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
2152
|
+
}
|
|
2153
|
+
return null;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
function formatWalletNet(value) {
|
|
2157
|
+
if (value === null) return 'not available';
|
|
2158
|
+
return `$${value.toLocaleString('en-US', { maximumFractionDigits: 2 })}`;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
function formatCents(value, currency = 'usd') {
|
|
2162
|
+
const amount = firstNumber(value) / 100;
|
|
2163
|
+
const code = firstString(currency).toUpperCase() || 'USD';
|
|
2164
|
+
const formatted = amount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
2165
|
+
return code === 'USD' ? `$${formatted}` : `${code} ${formatted}`;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
function businessOperationError(message) {
|
|
2169
|
+
console.error(message);
|
|
2170
|
+
process.exitCode = 1;
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
async function resolveBusinessOperation(slug, usage) {
|
|
2175
|
+
const requestedSlug = detectBusinessSlug(slug);
|
|
2176
|
+
if (!requestedSlug) return businessOperationError(`No business specified. Usage: ${usage}`);
|
|
2177
|
+
|
|
2178
|
+
const creds = loadCredentials();
|
|
2179
|
+
if (!creds || !creds.token) return businessOperationError('Not logged in. Run: atris login');
|
|
2180
|
+
|
|
2181
|
+
const resolved = await resolveSlug(requestedSlug, creds);
|
|
2182
|
+
if (!resolved) return businessOperationError(`Business "${requestedSlug}" not found.`);
|
|
2183
|
+
return { requestedSlug, resolved, token: creds.token };
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
async function fetchBusinessDashboard(context) {
|
|
2187
|
+
const result = await apiRequestJson(`/business/${context.resolved.business_id}/dashboard`, {
|
|
2188
|
+
method: 'GET',
|
|
2189
|
+
token: context.token,
|
|
2190
|
+
timeoutMs: 120000,
|
|
2191
|
+
});
|
|
2192
|
+
if (!result.ok) {
|
|
2193
|
+
const detail = result.errorMessage || result.error || `HTTP ${result.status}`;
|
|
2194
|
+
return businessOperationError(`Could not load business room: ${detail}`);
|
|
2195
|
+
}
|
|
2196
|
+
return result.data || {};
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
async function fetchBusinessOrders(context, limit = 50) {
|
|
2200
|
+
return apiRequestJson(`/storefront/${context.resolved.business_id}/orders?limit=${limit}`, {
|
|
2201
|
+
method: 'GET',
|
|
2202
|
+
token: context.token,
|
|
2203
|
+
timeoutMs: 120000,
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
function renderOrderLine(order) {
|
|
2208
|
+
const kind = order.isPreorder ? ', preorder' : '';
|
|
2209
|
+
const when = order.createdAt ? relativeTime(order.createdAt) : 'date unavailable';
|
|
2210
|
+
return ` ${order.reference}: ${order.product} x${order.quantity}, ${order.buyer}, ${when}${kind}`;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
async function businessRoom(slug) {
|
|
2214
|
+
const context = await resolveBusinessOperation(slug, 'atris business room <slug>');
|
|
2215
|
+
if (!context) return;
|
|
2216
|
+
|
|
2217
|
+
const [dashboard, ordersResult] = await Promise.all([
|
|
2218
|
+
fetchBusinessDashboard(context),
|
|
2219
|
+
fetchBusinessOrders(context, 5),
|
|
2220
|
+
]);
|
|
2221
|
+
if (!dashboard) return;
|
|
2222
|
+
|
|
2223
|
+
const business = asRecord(dashboard.business);
|
|
2224
|
+
const roster = asRecord(dashboard.roster);
|
|
2225
|
+
const members = Array.isArray(roster.members)
|
|
2226
|
+
? roster.members
|
|
2227
|
+
: (Array.isArray(business.members) ? business.members : []);
|
|
2228
|
+
const storefront = readStorefront(business);
|
|
2229
|
+
const orders = ordersResult.ok ? normalizeOrdersPayload(ordersResult.data).orders.slice(0, 5) : [];
|
|
2230
|
+
const name = business.name || context.resolved.name || context.requestedSlug;
|
|
2231
|
+
const appCount = Number.isFinite(business.app_count)
|
|
2232
|
+
? business.app_count
|
|
2233
|
+
: (Array.isArray(business.apps) ? business.apps.length : 0);
|
|
2234
|
+
|
|
2235
|
+
console.log('');
|
|
2236
|
+
console.log(`business room: ${name}`);
|
|
2237
|
+
console.log(`wallet/net: ${formatWalletNet(readWalletNet(business))}`);
|
|
2238
|
+
console.log(`members: ${members.length}`);
|
|
2239
|
+
for (const member of members) {
|
|
2240
|
+
const agent = asRecord(member.atris);
|
|
2241
|
+
const memberName = firstString(member.display_name, agent.agent_name, member.name, member.email) || 'unknown';
|
|
2242
|
+
console.log(` ${memberName} (${member.role || 'member'})`);
|
|
2243
|
+
}
|
|
2244
|
+
console.log(`apps: ${appCount}`);
|
|
2245
|
+
console.log(`store: ${storefront.enabled ? 'enabled' : 'disabled'}, ${storefront.products.length} products`);
|
|
2246
|
+
console.log('recent orders:');
|
|
2247
|
+
if (!ordersResult.ok) {
|
|
2248
|
+
const detail = ordersResult.errorMessage || ordersResult.error || `HTTP ${ordersResult.status}`;
|
|
2249
|
+
console.log(` unavailable: ${detail}`);
|
|
2250
|
+
process.exitCode = 1;
|
|
2251
|
+
} else if (orders.length === 0) {
|
|
2252
|
+
console.log(' none');
|
|
2253
|
+
} else {
|
|
2254
|
+
orders.forEach(order => console.log(renderOrderLine(order)));
|
|
2255
|
+
}
|
|
2256
|
+
console.log('');
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
async function businessProducts(slug) {
|
|
2260
|
+
const context = await resolveBusinessOperation(slug, 'atris business products <slug>');
|
|
2261
|
+
if (!context) return;
|
|
2262
|
+
const dashboard = await fetchBusinessDashboard(context);
|
|
2263
|
+
if (!dashboard) return;
|
|
2264
|
+
|
|
2265
|
+
const business = asRecord(dashboard.business);
|
|
2266
|
+
const storefront = readStorefront(business);
|
|
2267
|
+
const name = business.name || context.resolved.name || context.requestedSlug;
|
|
2268
|
+
console.log('');
|
|
2269
|
+
console.log(`products: ${name} (${storefront.products.length})`);
|
|
2270
|
+
if (storefront.products.length === 0) {
|
|
2271
|
+
console.log(' none');
|
|
2272
|
+
} else {
|
|
2273
|
+
for (const product of storefront.products) {
|
|
2274
|
+
const productName = firstString(product.name) || 'unnamed product';
|
|
2275
|
+
const productId = firstString(product.id) || 'no id';
|
|
2276
|
+
console.log(` ${productName} (${productId}): ${formatCents(product.price_cents, product.currency)}`);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
console.log('');
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
async function businessOrders(slug) {
|
|
2283
|
+
const context = await resolveBusinessOperation(slug, 'atris business orders <slug>');
|
|
2284
|
+
if (!context) return;
|
|
2285
|
+
const result = await fetchBusinessOrders(context, 50);
|
|
2286
|
+
if (!result.ok) {
|
|
2287
|
+
const detail = result.errorMessage || result.error || `HTTP ${result.status}`;
|
|
2288
|
+
businessOperationError(`Could not load orders: ${detail}`);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
const data = normalizeOrdersPayload(result.data);
|
|
2293
|
+
const name = context.resolved.name || context.requestedSlug;
|
|
2294
|
+
console.log('');
|
|
2295
|
+
console.log(`orders: ${name} (${data.orders.length})`);
|
|
2296
|
+
console.log(`revenue: ${formatCents(data.revenueCents)}`);
|
|
2297
|
+
console.log(`paid orders: ${data.paidOrders}`);
|
|
2298
|
+
console.log(`pending revenue: ${formatCents(data.pendingRevenueCents)}`);
|
|
2299
|
+
if (data.orders.length === 0) {
|
|
2300
|
+
console.log(' none');
|
|
2301
|
+
} else {
|
|
2302
|
+
data.orders.forEach(order => console.log(renderOrderLine(order)));
|
|
2303
|
+
}
|
|
2304
|
+
console.log('');
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
function parseBusinessStoreArgs(args = []) {
|
|
2308
|
+
const actions = new Set(['status', 'on', 'enable', 'toggle', 'off', 'disable']);
|
|
2309
|
+
if (actions.has(args[0])) return { action: args[0], slug: args[1] };
|
|
2310
|
+
if (actions.has(args[1])) return { action: args[1], slug: args[0] };
|
|
2311
|
+
return { action: 'status', slug: args[0] };
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
async function businessStore(args = []) {
|
|
2315
|
+
const { action, slug } = parseBusinessStoreArgs(args);
|
|
2316
|
+
if (action === 'off' || action === 'disable') {
|
|
2317
|
+
businessOperationError('The production store API does not support disabling stores yet.');
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
const context = await resolveBusinessOperation(slug, 'atris business store <on|status> [slug]');
|
|
2322
|
+
if (!context) return;
|
|
2323
|
+
const dashboard = await fetchBusinessDashboard(context);
|
|
2324
|
+
if (!dashboard) return;
|
|
2325
|
+
|
|
2326
|
+
const business = asRecord(dashboard.business);
|
|
2327
|
+
const storefront = readStorefront(business);
|
|
2328
|
+
const name = business.name || context.resolved.name || context.requestedSlug;
|
|
2329
|
+
if (action === 'status') {
|
|
2330
|
+
console.log(`store: ${storefront.enabled ? 'enabled' : 'disabled'} for ${name}, ${storefront.products.length} products`);
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
if (storefront.enabled) {
|
|
2334
|
+
console.log(`store: already enabled for ${name}, ${storefront.products.length} products`);
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
const result = await apiRequestJson(`/storefront/${context.resolved.business_id}/products`, {
|
|
2339
|
+
method: 'PUT',
|
|
2340
|
+
token: context.token,
|
|
2341
|
+
body: { products: storefront.products },
|
|
2342
|
+
timeoutMs: 120000,
|
|
2343
|
+
});
|
|
2344
|
+
if (!result.ok) {
|
|
2345
|
+
const detail = result.errorMessage || result.error || `HTTP ${result.status}`;
|
|
2346
|
+
businessOperationError(`Could not enable store: ${detail}`);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
const enabledProducts = Array.isArray(result.data?.products) ? result.data.products.length : storefront.products.length;
|
|
2350
|
+
console.log(`store: enabled for ${name}, ${enabledProducts} products`);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2031
2353
|
// ---------------------------------------------------------------------------
|
|
2032
2354
|
// atris business health <slug>
|
|
2033
2355
|
// ---------------------------------------------------------------------------
|
|
@@ -3045,6 +3367,10 @@ function printBusinessHelp() {
|
|
|
3045
3367
|
console.log(' simulate <idea> Create a business, four-role team, missions, and endgame loop');
|
|
3046
3368
|
console.log(' add <slug> Register an existing cloud business');
|
|
3047
3369
|
console.log(' list Show registered businesses');
|
|
3370
|
+
console.log(' room [slug] Show wallet, members, apps, store, and recent orders');
|
|
3371
|
+
console.log(' products [slug] List store products');
|
|
3372
|
+
console.log(' orders [slug] List store orders');
|
|
3373
|
+
console.log(' store on [slug] Enable the store with its current catalog');
|
|
3048
3374
|
console.log(' team [slug] Show members, roles, and admin access');
|
|
3049
3375
|
console.log(' status <slug> Quick status check');
|
|
3050
3376
|
console.log(' health [slug] Full health dashboard');
|
|
@@ -3122,6 +3448,21 @@ async function businessCommand(subcommand, ...args) {
|
|
|
3122
3448
|
case 'health':
|
|
3123
3449
|
await businessHealth(args[0]);
|
|
3124
3450
|
break;
|
|
3451
|
+
case 'room':
|
|
3452
|
+
await businessRoom(args[0]);
|
|
3453
|
+
break;
|
|
3454
|
+
case 'products':
|
|
3455
|
+
case 'product':
|
|
3456
|
+
await businessProducts(args[0]);
|
|
3457
|
+
break;
|
|
3458
|
+
case 'orders':
|
|
3459
|
+
case 'order':
|
|
3460
|
+
await businessOrders(args[0]);
|
|
3461
|
+
break;
|
|
3462
|
+
case 'store':
|
|
3463
|
+
case 'storefront':
|
|
3464
|
+
await businessStore(args);
|
|
3465
|
+
break;
|
|
3125
3466
|
case 'team':
|
|
3126
3467
|
case 'members':
|
|
3127
3468
|
case 'roster':
|
|
@@ -3189,4 +3530,8 @@ module.exports = {
|
|
|
3189
3530
|
collectBusinessShareState,
|
|
3190
3531
|
renderBusinessCreatedNextSteps,
|
|
3191
3532
|
recordBusinessRun,
|
|
3533
|
+
normalizeOrdersPayload,
|
|
3534
|
+
parseBusinessStoreArgs,
|
|
3535
|
+
readStorefront,
|
|
3536
|
+
readWalletNet,
|
|
3192
3537
|
};
|
package/commands/chat-scan.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
3
4
|
const { scanChatLogs, writeLatestScan } = require('../lib/chat-log-scan');
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function readFlag(args, name, fallback) {
|
|
6
|
+
// Preserve the existing rule that the next token is a value, even if it is a flag.
|
|
7
|
+
function readFollowingFlag(args, name, fallback) {
|
|
10
8
|
const i = args.indexOf(name);
|
|
11
9
|
if (i === -1 || i === args.length - 1) return fallback;
|
|
12
10
|
return args[i + 1];
|
|
@@ -79,8 +77,8 @@ Options:
|
|
|
79
77
|
|
|
80
78
|
const report = scanChatLogs({
|
|
81
79
|
cwd: root,
|
|
82
|
-
hours:
|
|
83
|
-
limit:
|
|
80
|
+
hours: readFollowingFlag(args, '--hours', 720),
|
|
81
|
+
limit: readFollowingFlag(args, '--limit', 12),
|
|
84
82
|
});
|
|
85
83
|
|
|
86
84
|
let latestPath = null;
|
package/commands/codex-goal.js
CHANGED
|
@@ -2,15 +2,13 @@ const fs = require('fs');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { spawnSync } = require('child_process');
|
|
5
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
5
6
|
|
|
6
7
|
const SCHEMA = 'atris.codex_goal.v1';
|
|
7
8
|
const CONFIRM_RESET_FLAG = '--confirm-complete-goal-reset';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function readFlag(args, name, fallback = '') {
|
|
10
|
+
// Preserve the existing rule that the next token is a value, even if it is a flag.
|
|
11
|
+
function readFollowingFlag(args, name, fallback = '') {
|
|
14
12
|
const index = args.indexOf(name);
|
|
15
13
|
if (index === -1 || index + 1 >= args.length) return fallback;
|
|
16
14
|
return args[index + 1];
|
|
@@ -32,7 +30,7 @@ function sqlString(value) {
|
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
function resolveStatePath(args = []) {
|
|
35
|
-
const explicit =
|
|
33
|
+
const explicit = readFollowingFlag(args, '--state', process.env.CODEX_STATE_DB || '');
|
|
36
34
|
if (explicit) return path.resolve(expandHome(explicit));
|
|
37
35
|
// Codex moved native goals into ~/.codex/goals_1.sqlite; older builds kept them in state_5.sqlite.
|
|
38
36
|
// Prefer the live goals DB so the bridge sees real goal activity, fall back to the legacy state DB.
|
|
@@ -43,7 +41,7 @@ function resolveStatePath(args = []) {
|
|
|
43
41
|
|
|
44
42
|
// Thread metadata (cwd/title) stayed in state_5.sqlite even after goals moved to goals_1.sqlite.
|
|
45
43
|
function resolveThreadsPath(args = []) {
|
|
46
|
-
const explicit =
|
|
44
|
+
const explicit = readFollowingFlag(args, '--threads-db', process.env.CODEX_THREADS_DB || '');
|
|
47
45
|
if (explicit) return path.resolve(expandHome(explicit));
|
|
48
46
|
const legacyDb = path.join(os.homedir(), '.codex', 'state_5.sqlite');
|
|
49
47
|
return fs.existsSync(legacyDb) ? path.resolve(legacyDb) : '';
|
|
@@ -79,7 +77,7 @@ function runGoalQuery(args, buildSql) {
|
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
function defaultRunsDir(args = []) {
|
|
82
|
-
return path.resolve(
|
|
80
|
+
return path.resolve(readFollowingFlag(args, '--out-dir', path.join(process.cwd(), '.atris', 'runs')));
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
function ensurePrivateDir(dir) {
|
|
@@ -196,7 +194,7 @@ function readRecentGoals(args, limit = 10) {
|
|
|
196
194
|
}
|
|
197
195
|
|
|
198
196
|
function resolveThreadGoal(args) {
|
|
199
|
-
const explicitThread =
|
|
197
|
+
const explicitThread = readFollowingFlag(args, '--thread', '');
|
|
200
198
|
if (explicitThread) return readGoalByThread(args, explicitThread);
|
|
201
199
|
if (hasFlag(args, '--latest')) return readLatestGoalForCwd(args, process.cwd());
|
|
202
200
|
const envThread = process.env.CODEX_THREAD_ID || '';
|
|
@@ -248,7 +246,7 @@ function statusCommand(args) {
|
|
|
248
246
|
return;
|
|
249
247
|
}
|
|
250
248
|
|
|
251
|
-
const limit = Math.max(1, Math.min(50, Number(
|
|
249
|
+
const limit = Math.max(1, Math.min(50, Number(readFollowingFlag(args, '--limit', '10')) || 10));
|
|
252
250
|
const goals = readRecentGoals(args, limit);
|
|
253
251
|
const payload = { ok: true, schema: SCHEMA, action: 'status', state_path: dbPath, goals };
|
|
254
252
|
printJsonOrText(payload, [
|
package/commands/console.js
CHANGED
|
@@ -315,18 +315,34 @@ function checkAuth(backend) {
|
|
|
315
315
|
function launchClaude(systemPrompt, extraArgs, options = {}) {
|
|
316
316
|
const skipPermissions = options.skipPermissions !== false;
|
|
317
317
|
const runnerBin = resolveClaudeRunnerBin();
|
|
318
|
+
const promptStdin = typeof options.promptStdin === 'string'
|
|
319
|
+
? options.promptStdin
|
|
320
|
+
: null;
|
|
318
321
|
const args = [
|
|
319
322
|
...(skipPermissions ? ['--dangerously-skip-permissions'] : []),
|
|
323
|
+
...(promptStdin !== null ? ['--print'] : []),
|
|
320
324
|
'--append-system-prompt', systemPrompt,
|
|
321
325
|
...extraArgs,
|
|
322
326
|
];
|
|
323
327
|
|
|
324
328
|
const child = spawnSync(runnerBin, args, {
|
|
325
329
|
cwd: process.cwd(),
|
|
326
|
-
stdio: 'inherit',
|
|
327
|
-
|
|
330
|
+
stdio: promptStdin === null ? 'inherit' : ['pipe', 'inherit', 'inherit'],
|
|
331
|
+
...(promptStdin === null ? {} : { input: promptStdin }),
|
|
332
|
+
env: { ...process.env, ...(options.runnerEnv || {}), CLAUDECODE: undefined },
|
|
328
333
|
});
|
|
329
334
|
|
|
335
|
+
try {
|
|
336
|
+
if (typeof options.onRunnerExit === 'function') {
|
|
337
|
+
options.onRunnerExit({ status: child.status ?? 1, signal: child.signal || null });
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error(`warning: could not finalize runner receipt: ${error.message}`);
|
|
341
|
+
}
|
|
342
|
+
for (const cleanupPath of options.cleanupPaths || []) {
|
|
343
|
+
try { fs.rmSync(cleanupPath, { recursive: true, force: true }); } catch {}
|
|
344
|
+
}
|
|
345
|
+
|
|
330
346
|
if (child.error) {
|
|
331
347
|
console.error(`✗ Failed to start ${runnerBin}: ${child.error.message}`);
|
|
332
348
|
process.exit(1);
|
|
@@ -422,7 +438,7 @@ function consoleCommand(options = {}) {
|
|
|
422
438
|
|
|
423
439
|
// Launch
|
|
424
440
|
if (backend === 'claude') {
|
|
425
|
-
launchClaude(systemPrompt, extraArgs, { skipPermissions });
|
|
441
|
+
launchClaude(systemPrompt, extraArgs, { ...options, skipPermissions });
|
|
426
442
|
} else {
|
|
427
443
|
launchCodex(systemPrompt, extraArgs);
|
|
428
444
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
answerMissionHumanAsk,
|
|
5
|
+
listMissions,
|
|
6
|
+
listWorktreeRollupMissions,
|
|
7
|
+
pingMission,
|
|
8
|
+
} = require('./mission');
|
|
9
|
+
const { openHumanAsks, normalizeHumanAsks } = require('../lib/mission-human-asks');
|
|
10
|
+
const { redirectToWorkspaceRoot } = require('../lib/mission-root');
|
|
11
|
+
const { shortId } = require('../lib/short-name');
|
|
12
|
+
|
|
13
|
+
const TERMINAL_STATUSES = new Set(['stopped', 'complete']);
|
|
14
|
+
|
|
15
|
+
function missionTouchedAt(mission) {
|
|
16
|
+
return String(mission.updated_at || mission.created_at || '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function liveMissions(root = process.cwd()) {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
return [...listMissions(root), ...listWorktreeRollupMissions(root)]
|
|
22
|
+
.filter((mission) => {
|
|
23
|
+
if (!mission || !mission.id || seen.has(mission.id) || TERMINAL_STATUSES.has(mission.status)) return false;
|
|
24
|
+
seen.add(mission.id);
|
|
25
|
+
return true;
|
|
26
|
+
})
|
|
27
|
+
.sort((left, right) => (
|
|
28
|
+
missionTouchedAt(right).localeCompare(missionTouchedAt(left))
|
|
29
|
+
|| String(left.id).localeCompare(String(right.id))
|
|
30
|
+
));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectOpenDecisions(root = process.cwd()) {
|
|
34
|
+
const decisions = [];
|
|
35
|
+
for (const mission of liveMissions(root)) {
|
|
36
|
+
const normalized = normalizeHumanAsks(mission.human_asks);
|
|
37
|
+
normalized.forEach((ask, askIndex) => {
|
|
38
|
+
if (!ask.text.trim() || ask.answered_at) return;
|
|
39
|
+
decisions.push({
|
|
40
|
+
number: decisions.length + 1,
|
|
41
|
+
owner: String(mission.owner || 'unowned'),
|
|
42
|
+
mission_id: mission.id,
|
|
43
|
+
mission_short_id: shortId(mission.id),
|
|
44
|
+
mission_status: mission.status,
|
|
45
|
+
mission_updated_at: missionTouchedAt(mission),
|
|
46
|
+
ask_index: askIndex,
|
|
47
|
+
text: ask.text,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return decisions;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function printHelp() {
|
|
55
|
+
console.log('Usage:');
|
|
56
|
+
console.log(' atris decide');
|
|
57
|
+
console.log(' atris decide <n> y|n|yes|no [--note "<text>"]');
|
|
58
|
+
console.log(' atris decide --json');
|
|
59
|
+
console.log(' atris decide <n> y --json');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function fail(message, asJson, code = 2) {
|
|
63
|
+
if (asJson) {
|
|
64
|
+
console.log(JSON.stringify({ ok: false, action: 'decide_error', error: message }));
|
|
65
|
+
} else {
|
|
66
|
+
console.error(message);
|
|
67
|
+
}
|
|
68
|
+
process.exitCode = code;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseArgs(args) {
|
|
72
|
+
const asJson = args.includes('--json');
|
|
73
|
+
const rest = args.filter((arg) => arg !== '--json');
|
|
74
|
+
let note = '';
|
|
75
|
+
const noteIndex = rest.findIndex((arg) => arg === '--note' || String(arg).startsWith('--note='));
|
|
76
|
+
if (noteIndex !== -1) {
|
|
77
|
+
const noteArg = String(rest[noteIndex]);
|
|
78
|
+
if (noteArg === '--note') {
|
|
79
|
+
if (rest[noteIndex + 1] == null) return { asJson, error: '--note requires text' };
|
|
80
|
+
note = String(rest[noteIndex + 1]).trim();
|
|
81
|
+
rest.splice(noteIndex, 2);
|
|
82
|
+
} else {
|
|
83
|
+
note = noteArg.slice('--note='.length).trim();
|
|
84
|
+
rest.splice(noteIndex, 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { asJson, note, rest };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function decideCommand(args = []) {
|
|
91
|
+
redirectToWorkspaceRoot();
|
|
92
|
+
const parsed = parseArgs(args);
|
|
93
|
+
if (parsed.error) return fail(parsed.error, parsed.asJson);
|
|
94
|
+
const { asJson, note, rest } = parsed;
|
|
95
|
+
if (rest.includes('--help') || rest.includes('-h') || rest[0] === 'help') {
|
|
96
|
+
printHelp();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const decisions = collectOpenDecisions();
|
|
101
|
+
if (!rest.length) {
|
|
102
|
+
if (asJson) {
|
|
103
|
+
console.log(JSON.stringify({
|
|
104
|
+
ok: true,
|
|
105
|
+
action: 'decide_list',
|
|
106
|
+
count: decisions.length,
|
|
107
|
+
decisions,
|
|
108
|
+
}, null, 2));
|
|
109
|
+
} else if (!decisions.length) {
|
|
110
|
+
console.log('nothing is waiting for a decision.');
|
|
111
|
+
} else {
|
|
112
|
+
for (const decision of decisions) {
|
|
113
|
+
console.log(`[${decision.number}] ${decision.owner} · ${decision.mission_short_id} · ${decision.text}`);
|
|
114
|
+
}
|
|
115
|
+
console.log('atris decide <n> y|n');
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (rest.length !== 2) {
|
|
121
|
+
return fail('usage: atris decide <n> y|n|yes|no [--note "<text>"]', asJson);
|
|
122
|
+
}
|
|
123
|
+
const number = Number(rest[0]);
|
|
124
|
+
if (!Number.isInteger(number) || number < 1) {
|
|
125
|
+
return fail('decision number must be a positive integer', asJson);
|
|
126
|
+
}
|
|
127
|
+
const answerToken = String(rest[1]).toLowerCase();
|
|
128
|
+
const answer = answerToken === 'y' || answerToken === 'yes'
|
|
129
|
+
? 'yes'
|
|
130
|
+
: (answerToken === 'n' || answerToken === 'no' ? 'no' : null);
|
|
131
|
+
if (!answer) return fail('answer must be y, n, yes, or no', asJson);
|
|
132
|
+
const decision = decisions[number - 1];
|
|
133
|
+
if (!decision) return fail(`decision ${number} is not open`, asJson, 1);
|
|
134
|
+
|
|
135
|
+
const message = `Decision on "${decision.text}": ${answer.toUpperCase()}${note ? ` — ${note}` : ''}`;
|
|
136
|
+
pingMission([decision.mission_id, message, '--from', 'decide'], { silent: true });
|
|
137
|
+
const mission = answerMissionHumanAsk(decision.mission_id, decision.ask_index, answer, note);
|
|
138
|
+
const remainingOpenAsks = openHumanAsks(mission.human_asks).length;
|
|
139
|
+
const payload = {
|
|
140
|
+
ok: true,
|
|
141
|
+
action: 'decision_answered',
|
|
142
|
+
decision: {
|
|
143
|
+
...decision,
|
|
144
|
+
answer,
|
|
145
|
+
note,
|
|
146
|
+
message,
|
|
147
|
+
},
|
|
148
|
+
mission: {
|
|
149
|
+
id: mission.id,
|
|
150
|
+
short_id: decision.mission_short_id,
|
|
151
|
+
owner: mission.owner,
|
|
152
|
+
status: mission.status,
|
|
153
|
+
remaining_open_asks: remainingOpenAsks,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
if (asJson) {
|
|
157
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
158
|
+
} else {
|
|
159
|
+
console.log(`sent to ${decision.mission_short_id}: ${message}`);
|
|
160
|
+
console.log(`mission ${decision.mission_short_id} will read it on its next tick.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
decideCommand,
|
|
166
|
+
};
|
package/commands/deck.js
CHANGED
|
@@ -17,6 +17,7 @@ const fs = require('fs');
|
|
|
17
17
|
const https = require('https');
|
|
18
18
|
const os = require('os');
|
|
19
19
|
const path = require('path');
|
|
20
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
20
21
|
const { buildDeck, THEMES, notesRequests } = require('../lib/slides-deck');
|
|
21
22
|
const {
|
|
22
23
|
lintSpec,
|
|
@@ -118,10 +119,6 @@ function flag(argv, name) {
|
|
|
118
119
|
return i !== -1 ? argv[i + 1] : null;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
function hasFlag(argv, name) {
|
|
122
|
-
return argv.includes(name);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
122
|
// Flags that take a value, so their following token is consumed (not a path).
|
|
126
123
|
const VALUE_FLAGS = new Set(['--theme', '--title', '--update', '--out', '--style', '--url', '--md']);
|
|
127
124
|
|