@ravefox/space 0.2.1 → 0.4.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/LICENSE +21 -205
- package/README.md +104 -31
- package/bin/space.js +113 -0
- package/lib/agents.js +181 -0
- package/lib/boardTools.js +89 -0
- package/lib/credentials.js +95 -0
- package/lib/daemon.js +294 -0
- package/lib/harness.js +166 -0
- package/lib/http.js +101 -0
- package/lib/login.js +68 -0
- package/lib/mcp.js +39 -0
- package/lib/runtime.js +253 -0
- package/lib/store.js +96 -0
- package/lib/tools.js +93 -0
- package/lib/workspaceSandbox.js +77 -0
- package/lib/workspaceTools.js +160 -0
- package/lib/workspaceWorker.js +17 -0
- package/package.json +27 -46
- package/NOTICE +0 -55
- package/assets/webhook-sdk/python/bot.py +0 -63
- package/assets/webhook-sdk/python/commonly.py +0 -232
- package/node_modules/@commonlyai/seats/README.md +0 -25
- package/node_modules/@commonlyai/seats/package.json +0 -22
- package/node_modules/@commonlyai/seats/src/adapters/hermes.js +0 -264
- package/node_modules/@commonlyai/seats/src/adapters/stub.js +0 -24
- package/node_modules/@commonlyai/seats/src/contract.js +0 -48
- package/node_modules/@commonlyai/seats/src/failures.js +0 -44
- package/node_modules/@commonlyai/seats/src/harness-plan.js +0 -329
- package/node_modules/@commonlyai/seats/src/index.js +0 -22
- package/node_modules/@commonlyai/seats/src/process-group.js +0 -269
- package/node_modules/@commonlyai/seats/src/record/format.js +0 -737
- package/node_modules/@commonlyai/seats/src/record/otel.js +0 -238
- package/node_modules/@commonlyai/seats/src/runtime-observation.js +0 -29
- package/node_modules/@commonlyai/seats/src/seat-model.js +0 -17
- package/node_modules/@commonlyai/seats/src/supervisor.js +0 -308
- package/node_modules/@commonlyai/seats/src/trace/events.js +0 -46
- package/node_modules/@commonlyai/seats/src/trace/store.js +0 -276
- package/node_modules/@commonlyai/seats/src/turn-processes.js +0 -226
- package/node_modules/@commonlyai/seats/src/upstream-refusal.js +0 -418
- package/node_modules/@commonlyai/seats/src/valet/env.js +0 -201
- package/node_modules/@commonlyai/seats/src/valet/grant.js +0 -123
- package/node_modules/@commonlyai/seats/src/valet/permissions.js +0 -213
- package/node_modules/@commonlyai/seats/src/wire/provider-proxy.js +0 -529
- package/skills/commonly/SKILL.md +0 -233
- package/src/commands/agent.js +0 -6334
- package/src/commands/daemon.js +0 -933
- package/src/commands/dev.js +0 -153
- package/src/commands/doctor.js +0 -141
- package/src/commands/inbox.js +0 -330
- package/src/commands/login.js +0 -194
- package/src/commands/mcp.js +0 -56
- package/src/commands/pod.js +0 -120
- package/src/commands/task.js +0 -279
- package/src/commands/verify.js +0 -48
- package/src/index.js +0 -99
- package/src/lib/adapter-login.js +0 -76
- package/src/lib/adapters/broker-bridge-entry.js +0 -76
- package/src/lib/adapters/claude.js +0 -2207
- package/src/lib/adapters/codex-broker-bridge.mjs +0 -298
- package/src/lib/adapters/codex.js +0 -1898
- package/src/lib/adapters/dsh.js +0 -513
- package/src/lib/adapters/goose.js +0 -510
- package/src/lib/adapters/hermes.js +0 -18
- package/src/lib/adapters/index.js +0 -68
- package/src/lib/adapters/opencode.js +0 -1681
- package/src/lib/adapters/pi-commonly-mcp.mjs +0 -71
- package/src/lib/adapters/pi-mcp-client.mjs +0 -617
- package/src/lib/adapters/pi-shell-env.mjs +0 -41
- package/src/lib/adapters/pi.js +0 -1025
- package/src/lib/adapters/qwen.js +0 -523
- package/src/lib/adapters/seat-model.js +0 -8
- package/src/lib/adapters/stub.js +0 -1
- package/src/lib/adapters/tested-version.js +0 -106
- package/src/lib/api.js +0 -205
- package/src/lib/claim-outcome.js +0 -128
- package/src/lib/claimed-work-gate.js +0 -73
- package/src/lib/cli-hint.js +0 -33
- package/src/lib/commit-attribution.js +0 -391
- package/src/lib/config.js +0 -155
- package/src/lib/content-refusal.js +0 -94
- package/src/lib/credential-file.js +0 -208
- package/src/lib/daemon-agent-states.js +0 -95
- package/src/lib/daemon-hint.js +0 -18
- package/src/lib/daemon-logs.js +0 -43
- package/src/lib/daemon-service-windows.js +0 -263
- package/src/lib/daemon-service.js +0 -486
- package/src/lib/daemon-state.js +0 -84
- package/src/lib/daemon-store.js +0 -119
- package/src/lib/daemon-supervisor.js +0 -629
- package/src/lib/daemon-watch.js +0 -115
- package/src/lib/declared-mcp-guard.js +0 -284
- package/src/lib/default-environment.js +0 -207
- package/src/lib/device-login.js +0 -126
- package/src/lib/digest-frame.js +0 -151
- package/src/lib/doctor.js +0 -291
- package/src/lib/egress-proxy.js +0 -160
- package/src/lib/enforcement.js +0 -946
- package/src/lib/environment.js +0 -681
- package/src/lib/first-output-watchdog.js +0 -191
- package/src/lib/git-readonly-server.js +0 -138
- package/src/lib/git-readonly.js +0 -463
- package/src/lib/git-remote-commonly.mjs +0 -100
- package/src/lib/grant-broker-guard.js +0 -345
- package/src/lib/hooks-config.js +0 -238
- package/src/lib/host-gh-broker.js +0 -152
- package/src/lib/host-gh-shim.mjs +0 -98
- package/src/lib/inbox.js +0 -483
- package/src/lib/instance-proxy.js +0 -76
- package/src/lib/machine-host.js +0 -29
- package/src/lib/mcp-credential-delivery.js +0 -121
- package/src/lib/mcp-first-run.js +0 -50
- package/src/lib/mcp-home.js +0 -644
- package/src/lib/mcp-ready.js +0 -68
- package/src/lib/mcp-server-version.js +0 -123
- package/src/lib/mcp-token.js +0 -36
- package/src/lib/mcp-warm-child.mjs +0 -412
- package/src/lib/memory-bridge.js +0 -191
- package/src/lib/memory-import.js +0 -156
- package/src/lib/missing-harness.js +0 -31
- package/src/lib/model-unavailable.js +0 -154
- package/src/lib/native-runtime-inventory.js +0 -40
- package/src/lib/outsider-grant-mcp.js +0 -82
- package/src/lib/own-key-launch.js +0 -42
- package/src/lib/pod-catch-up.js +0 -55
- package/src/lib/pod-focus.js +0 -224
- package/src/lib/pod-tail.js +0 -68
- package/src/lib/poll-liveness.js +0 -103
- package/src/lib/poll-retry.js +0 -81
- package/src/lib/poller.js +0 -200
- package/src/lib/provider-proxy.js +0 -42
- package/src/lib/public-seat-mcp-flag.js +0 -36
- package/src/lib/public-tool-error.js +0 -60
- package/src/lib/public-withheld-tools.js +0 -48
- package/src/lib/reply-receipt.js +0 -55
- package/src/lib/resolve-on-path.js +0 -22
- package/src/lib/runtime-inventory.js +0 -56
- package/src/lib/runtime-observation.js +0 -37
- package/src/lib/sandbox/bwrap.js +0 -275
- package/src/lib/sandbox/codex-perimeter.js +0 -16
- package/src/lib/sandbox/host-check.js +0 -268
- package/src/lib/sandbox/mode.js +0 -49
- package/src/lib/sandbox/opencode-loopback.mjs +0 -68
- package/src/lib/sandbox/seatbelt.js +0 -684
- package/src/lib/sandbox/turn-dir.js +0 -56
- package/src/lib/sandbox/windows-unsandboxed.js +0 -183
- package/src/lib/sandbox/workspace-git-config.js +0 -243
- package/src/lib/sandbox/workspace-pin.js +0 -158
- package/src/lib/seat-runtime-controls.js +0 -61
- package/src/lib/seat-stop.js +0 -97
- package/src/lib/server-skills.js +0 -32
- package/src/lib/session-recycle.js +0 -219
- package/src/lib/session-store.js +0 -215
- package/src/lib/skill-prompt.js +0 -74
- package/src/lib/skills-import.js +0 -113
- package/src/lib/spawn-credential.js +0 -402
- package/src/lib/spawn-retry.js +0 -311
- package/src/lib/stable-install.js +0 -176
- package/src/lib/supervised-turn.js +0 -352
- package/src/lib/task.js +0 -235
- package/src/lib/turn-failure-notice.js +0 -177
- package/src/lib/unconfined-seat-guard.js +0 -94
- package/src/lib/update-check.js +0 -192
- package/src/lib/upstream-refusal.js +0 -1
- package/src/lib/webhook-server.js +0 -109
- package/src/lib/windows-command.js +0 -393
- package/src/lib/workspace-git.js +0 -87
- package/src/lib/workspace-trust.js +0 -162
package/src/commands/login.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* commonly login [--instance <url>] [--key <name>]
|
|
3
|
-
*
|
|
4
|
-
* Authenticates and stores the token in ~/.commonly/config.json.
|
|
5
|
-
* Supports multiple named instances.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createInterface } from 'readline';
|
|
9
|
-
import { hostname } from 'os';
|
|
10
|
-
import { createClient, login as apiLogin } from '../lib/api.js';
|
|
11
|
-
import { DEFAULT_URL, resolveInstance, saveInstance } from '../lib/config.js';
|
|
12
|
-
import {
|
|
13
|
-
DeviceLoginCancelledError,
|
|
14
|
-
DeviceLoginDeniedError,
|
|
15
|
-
DeviceLoginExpiredError,
|
|
16
|
-
waitForDeviceAuthorization,
|
|
17
|
-
} from '../lib/device-login.js';
|
|
18
|
-
import { CLI } from '../lib/cli-hint.js';
|
|
19
|
-
|
|
20
|
-
const prompt = (rl, question) => new Promise((resolve) => rl.question(question, resolve));
|
|
21
|
-
|
|
22
|
-
const promptSecret = (question) => new Promise((resolve) => {
|
|
23
|
-
process.stdout.write(question);
|
|
24
|
-
const { stdin } = process;
|
|
25
|
-
stdin.setRawMode?.(true);
|
|
26
|
-
stdin.resume();
|
|
27
|
-
stdin.setEncoding('utf8');
|
|
28
|
-
|
|
29
|
-
let password = '';
|
|
30
|
-
const onData = (ch) => {
|
|
31
|
-
if (ch === '\n' || ch === '\r' || ch === '\u0003') {
|
|
32
|
-
stdin.setRawMode?.(false);
|
|
33
|
-
stdin.pause();
|
|
34
|
-
stdin.removeListener('data', onData);
|
|
35
|
-
process.stdout.write('\n');
|
|
36
|
-
resolve(password);
|
|
37
|
-
} else if (ch === '\u007f') {
|
|
38
|
-
password = password.slice(0, -1);
|
|
39
|
-
} else {
|
|
40
|
-
password += ch;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
stdin.on('data', onData);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Resolve the login target before making the request or writing config.
|
|
48
|
-
*
|
|
49
|
-
* `--instance` accepts either a URL or a saved profile key. The API client
|
|
50
|
-
* resolves keys internally, but persisting the raw argument would write a key
|
|
51
|
-
* (for example, "default") into the URL field and break every later command.
|
|
52
|
-
*/
|
|
53
|
-
export const resolveLoginTarget = ({ instanceArg, keyArg }) => {
|
|
54
|
-
// A missing --instance means "the active profile", just like the other
|
|
55
|
-
// config helpers. Resolve it before choosing the key so a login without
|
|
56
|
-
// flags refreshes that profile instead of silently forking `default`.
|
|
57
|
-
const resolved = resolveInstance(instanceArg);
|
|
58
|
-
const isUrl = /^https?:\/\//i.test(instanceArg || '');
|
|
59
|
-
const instanceUrl = (resolved?.url || (isUrl ? instanceArg : DEFAULT_URL)).replace(/\/$/, '');
|
|
60
|
-
const isLocal = instanceUrl.includes('localhost') || instanceUrl.includes('127.0.0.1');
|
|
61
|
-
const isKey = instanceArg && !isUrl;
|
|
62
|
-
// A URL may resolve back to an existing named profile too. Prefer that key
|
|
63
|
-
// regardless of input shape, or a URL login can fork a second profile onto
|
|
64
|
-
// the same endpoint and leave the original token stale.
|
|
65
|
-
const configKey = keyArg || resolved?.key || (isKey ? instanceArg : (isLocal ? 'local' : 'default'));
|
|
66
|
-
|
|
67
|
-
return { instanceUrl, configKey };
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export const registerLogin = (program) => {
|
|
71
|
-
program
|
|
72
|
-
.command('login')
|
|
73
|
-
.description('Authenticate to a Space instance')
|
|
74
|
-
.option('--instance <url-or-key>', 'Instance URL or saved profile key (default: active profile; production on first run)')
|
|
75
|
-
.option('--key <name>', 'Config key to save as (default: active profile key; otherwise derived from the instance)')
|
|
76
|
-
.option('--password', 'Use the legacy email/password prompt instead of device authorization')
|
|
77
|
-
.addHelpText('after', `
|
|
78
|
-
Examples:
|
|
79
|
-
$ ${CLI} login # active profile (production on first run)
|
|
80
|
-
$ ${CLI} login --instance https://api.commonly.me --key dev # named profile
|
|
81
|
-
$ ${CLI} login --instance http://localhost:5000 # saved as "local"
|
|
82
|
-
|
|
83
|
-
Tokens are stored in ~/.commonly/config.json. Other commands take
|
|
84
|
-
--instance <url-or-key> to target the right profile.
|
|
85
|
-
`)
|
|
86
|
-
.action(async (opts) => {
|
|
87
|
-
const { instanceUrl, configKey } = resolveLoginTarget({
|
|
88
|
-
instanceArg: opts.instance,
|
|
89
|
-
keyArg: opts.key,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
if (!opts.password) {
|
|
94
|
-
const client = createClient({ instance: instanceUrl, token: null });
|
|
95
|
-
const started = await client.post('/api/auth/device/start', {
|
|
96
|
-
clientName: 'commonly-cli',
|
|
97
|
-
clientVersion: program.version(),
|
|
98
|
-
hostname: hostname(),
|
|
99
|
-
});
|
|
100
|
-
const minutes = Math.ceil(started.expiresIn / 60);
|
|
101
|
-
console.log(`Logging in to ${instanceUrl} as a new device.\n`);
|
|
102
|
-
console.log(` Open ${started.verifyUrl}`);
|
|
103
|
-
console.log(` Code ${started.userCode}`);
|
|
104
|
-
console.log(`\nWaiting for approval… (expires in ${minutes}:00) press o to open the browser, q to cancel`);
|
|
105
|
-
const data = await waitForDeviceAuthorization({
|
|
106
|
-
client,
|
|
107
|
-
deviceCode: started.deviceCode,
|
|
108
|
-
userCode: started.userCode,
|
|
109
|
-
verifyUrl: started.verifyUrl,
|
|
110
|
-
interval: started.interval,
|
|
111
|
-
expiresIn: started.expiresIn,
|
|
112
|
-
onStatus: (message) => console.log(message),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
saveInstance({
|
|
116
|
-
key: configKey,
|
|
117
|
-
url: instanceUrl,
|
|
118
|
-
token: data.token,
|
|
119
|
-
userId: data.userId,
|
|
120
|
-
username: data.username,
|
|
121
|
-
tokenType: 'device',
|
|
122
|
-
});
|
|
123
|
-
const devicesUrl = new URL('/settings/devices', started.verifyUrl).toString();
|
|
124
|
-
console.log(`\n✓ Authorized as @${data.username} on ${configKey} (${instanceUrl})`);
|
|
125
|
-
console.log(` Token saved to ~/.commonly/config.json · manage devices at ${devicesUrl}`);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
console.log(`Logging in to ${instanceUrl}`);
|
|
130
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
131
|
-
const email = await prompt(rl, 'Email: ');
|
|
132
|
-
rl.close();
|
|
133
|
-
const password = await promptSecret('Password: ');
|
|
134
|
-
const data = await apiLogin(instanceUrl, email.trim(), password);
|
|
135
|
-
const token = data.token;
|
|
136
|
-
const userId = data.user?._id || data.user?.id;
|
|
137
|
-
const username = data.user?.username;
|
|
138
|
-
|
|
139
|
-
saveInstance({ key: configKey, url: instanceUrl, token, userId, username, tokenType: 'jwt' });
|
|
140
|
-
|
|
141
|
-
console.log(`\nLogged in as ${username} (${configKey})`);
|
|
142
|
-
console.log(`Token saved to ~/.commonly/config.json`);
|
|
143
|
-
} catch (err) {
|
|
144
|
-
const message = err instanceof DeviceLoginExpiredError
|
|
145
|
-
? `Code expired after 10 minutes. Run ${CLI} login --instance ${configKey} for a new code.`
|
|
146
|
-
: err instanceof DeviceLoginCancelledError || err instanceof DeviceLoginDeniedError
|
|
147
|
-
? err.message
|
|
148
|
-
: `Login failed: ${err.message}`;
|
|
149
|
-
console.error(message);
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
export const registerWhoami = (program) => {
|
|
156
|
-
program
|
|
157
|
-
.command('whoami')
|
|
158
|
-
.description('Show current auth state')
|
|
159
|
-
.option('--instance <url>', 'Target instance')
|
|
160
|
-
.action(async () => {
|
|
161
|
-
const { listInstances } = await import('../lib/config.js');
|
|
162
|
-
const instances = listInstances();
|
|
163
|
-
|
|
164
|
-
if (instances.length === 0) {
|
|
165
|
-
console.log(`Not logged in. Run: ${CLI} login`);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
instances.forEach(({ key, url, username, active, token, tokenType }) => {
|
|
170
|
-
const marker = active ? '→' : ' ';
|
|
171
|
-
console.log(`${marker} ${key} ${username || '?'}@${url} (${formatTokenStatus(token, tokenType, Date.now(), key)})`);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const decodeJwtExpiry = (token) => {
|
|
177
|
-
if (typeof token !== 'string' || token.split('.').length !== 3) return null;
|
|
178
|
-
try {
|
|
179
|
-
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));
|
|
180
|
-
return typeof payload.exp === 'number' ? payload.exp * 1000 : null;
|
|
181
|
-
} catch {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
export const formatTokenStatus = (token, tokenType, now = Date.now(), instanceKey = 'default') => {
|
|
187
|
-
if (tokenType === 'device' || String(token || '').startsWith('cm_')) return 'device token · no expiry';
|
|
188
|
-
const expiresAt = decodeJwtExpiry(token);
|
|
189
|
-
if (!expiresAt) return 'session token · expiry unknown';
|
|
190
|
-
const diff = expiresAt - now;
|
|
191
|
-
if (diff <= 0) return `expired — ${CLI} login --instance ${instanceKey}`;
|
|
192
|
-
const hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)));
|
|
193
|
-
return `expires in ${hours}h`;
|
|
194
|
-
};
|
package/src/commands/mcp.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* commonly mcp config — print an MCP server config for a locally attached agent (TASK-666).
|
|
3
|
-
* The MCP server only accepts a cm_agent_* runtime token, so the config points
|
|
4
|
-
* COMMONLY_TOKEN_FILE at a 0600 copy of that token and never prints the token.
|
|
5
|
-
*/
|
|
6
|
-
import { listLocalAgents, loadAgentToken } from './agent.js';
|
|
7
|
-
import { validMcpTokenName, writeMcpTokenCopy } from '../lib/mcp-token.js';
|
|
8
|
-
import { CLI } from '../lib/cli-hint.js';
|
|
9
|
-
|
|
10
|
-
const NO_AGENT_HINT = `Attach one first: ${CLI} agent attach <adapter> --pod <podId> --name <name>`;
|
|
11
|
-
|
|
12
|
-
export const buildMcpConfig = ({ agent } = {}) => {
|
|
13
|
-
let name = agent;
|
|
14
|
-
if (!name) {
|
|
15
|
-
const local = listLocalAgents();
|
|
16
|
-
if (local.length === 0) throw new Error(`No local agent has a runtime token on this computer. ${NO_AGENT_HINT}`);
|
|
17
|
-
if (local.length > 1) {
|
|
18
|
-
throw new Error(`More than one local agent (${local.map((a) => a.name).join(', ')}). Pick one with --agent <name>.`);
|
|
19
|
-
}
|
|
20
|
-
name = local[0].name;
|
|
21
|
-
}
|
|
22
|
-
if (!validMcpTokenName(name)) throw new Error(`Invalid agent name '${name}'`);
|
|
23
|
-
const record = loadAgentToken(name);
|
|
24
|
-
const token = String(record?.runtimeToken || '').trim();
|
|
25
|
-
if (!token) throw new Error(`No runtime token saved for agent '${name}'. ${NO_AGENT_HINT}`);
|
|
26
|
-
if (!token.startsWith('cm_agent_')) throw new Error(`The saved token for '${name}' is not a cm_agent_* runtime token. Re-attach it: ${CLI} agent attach`);
|
|
27
|
-
if (!record.instanceUrl) throw new Error(`Agent '${name}' has no saved instance URL. Re-attach it.`);
|
|
28
|
-
|
|
29
|
-
const tokenPath = writeMcpTokenCopy(name, token);
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
mcpServers: {
|
|
33
|
-
commonly: {
|
|
34
|
-
command: 'npx',
|
|
35
|
-
args: ['-y', '@commonlyai/mcp@latest'],
|
|
36
|
-
env: { COMMONLY_API_URL: record.instanceUrl, COMMONLY_TOKEN_FILE: tokenPath },
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const registerMcp = (program) => {
|
|
43
|
-
const mcp = program.command('mcp').description('Connect an MCP client (Claude Code, Codex, Cursor) to Space');
|
|
44
|
-
mcp
|
|
45
|
-
.command('config')
|
|
46
|
-
.description('Print an MCP server config for a locally attached agent; the token stays in a 0600 file')
|
|
47
|
-
.option('--agent <name>', 'Local agent to use (default: the only one)')
|
|
48
|
-
.action((opts) => {
|
|
49
|
-
try {
|
|
50
|
-
console.log(JSON.stringify(buildMcpConfig({ agent: opts.agent }), null, 2));
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.error(error.message);
|
|
53
|
-
process.exitCode = 1;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
};
|
package/src/commands/pod.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* commonly pod <subcommand>
|
|
3
|
-
*
|
|
4
|
-
* list — list pods you're a member of
|
|
5
|
-
* send — post a message to a pod
|
|
6
|
-
* tail — watch a pod's messages live
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { createClient } from '../lib/api.js';
|
|
10
|
-
import { getToken, resolveInstanceUrl } from '../lib/config.js';
|
|
11
|
-
import { createTailPoller } from '../lib/pod-tail.js';
|
|
12
|
-
import { CLI } from '../lib/cli-hint.js';
|
|
13
|
-
|
|
14
|
-
export const registerPod = (program) => {
|
|
15
|
-
const pod = program.command('pod').description('Manage pods');
|
|
16
|
-
|
|
17
|
-
pod.addHelpText('after', `
|
|
18
|
-
Examples:
|
|
19
|
-
$ ${CLI} pod list
|
|
20
|
-
$ ${CLI} pod send <podId> "hello from the CLI"
|
|
21
|
-
$ ${CLI} pod tail <podId>
|
|
22
|
-
|
|
23
|
-
Each command accepts --instance <url-or-key> to target a non-default
|
|
24
|
-
Space instance (see: ${CLI} login --help).
|
|
25
|
-
`);
|
|
26
|
-
|
|
27
|
-
// ── list ──────────────────────────────────────────────────────────────────
|
|
28
|
-
pod
|
|
29
|
-
.command('list')
|
|
30
|
-
.description('List pods you are a member of')
|
|
31
|
-
.option('--instance <url>', 'Target Space instance')
|
|
32
|
-
.action(async (opts) => {
|
|
33
|
-
const token = getToken(opts.instance);
|
|
34
|
-
if (!token) { console.error(`Not logged in. Run: ${CLI} login`); process.exit(1); }
|
|
35
|
-
|
|
36
|
-
const client = createClient({ instance: resolveInstanceUrl(opts.instance), token });
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const data = await client.get('/api/pods');
|
|
40
|
-
const pods = Array.isArray(data) ? data : data.pods || [];
|
|
41
|
-
|
|
42
|
-
if (pods.length === 0) { console.log('No pods found.'); return; }
|
|
43
|
-
|
|
44
|
-
const col = (s, w) => String(s ?? '').padEnd(w).slice(0, w);
|
|
45
|
-
console.log(`${col('NAME', 24)} ${col('TYPE', 12)} ${col('MEMBERS', 8)} ID`);
|
|
46
|
-
console.log('─'.repeat(70));
|
|
47
|
-
pods.forEach((p) => {
|
|
48
|
-
console.log(`${col(p.name, 24)} ${col(p.type || 'chat', 12)} ${col(p.memberCount ?? p.members?.length ?? '?', 8)} ${p._id}`);
|
|
49
|
-
});
|
|
50
|
-
} catch (err) {
|
|
51
|
-
console.error(`Failed: ${err.message}`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// ── send ──────────────────────────────────────────────────────────────────
|
|
57
|
-
pod
|
|
58
|
-
.command('send <podId> <message>')
|
|
59
|
-
.description('Post a message to a pod')
|
|
60
|
-
.option('--payload <json>', 'UI card JSON envelope; the message remains the required text fallback')
|
|
61
|
-
.option('--instance <url>', 'Target Space instance')
|
|
62
|
-
.action(async (podId, message, opts) => {
|
|
63
|
-
const token = getToken(opts.instance);
|
|
64
|
-
if (!token) { console.error(`Not logged in. Run: ${CLI} login`); process.exit(1); }
|
|
65
|
-
|
|
66
|
-
const client = createClient({ instance: resolveInstanceUrl(opts.instance), token });
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const body = { content: message };
|
|
70
|
-
if (opts.payload !== undefined) {
|
|
71
|
-
body.payload = JSON.parse(opts.payload);
|
|
72
|
-
body.messageType = 'card';
|
|
73
|
-
}
|
|
74
|
-
// UI cards use the validated agent endpoint only. A validation or
|
|
75
|
-
// authorization refusal must never retry through the human writer.
|
|
76
|
-
const result = await client.post(`/api/agents/runtime/pods/${podId}/messages`, body)
|
|
77
|
-
.catch((error) => {
|
|
78
|
-
if (opts.payload !== undefined) throw error;
|
|
79
|
-
return client.post(`/api/messages/${podId}`, body);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
console.log(`Sent (${result._id || result.id || 'ok'})`);
|
|
83
|
-
} catch (err) {
|
|
84
|
-
console.error(`Failed: ${err.message}`);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ── tail ──────────────────────────────────────────────────────────────────
|
|
90
|
-
pod
|
|
91
|
-
.command('tail <podId>')
|
|
92
|
-
.description('Watch pod messages live')
|
|
93
|
-
.option('--filter <type>', 'Filter by sender type: agents, humans, all', 'all')
|
|
94
|
-
.option('--instance <url>', 'Target Space instance')
|
|
95
|
-
.action(async (podId, opts) => {
|
|
96
|
-
const token = getToken(opts.instance);
|
|
97
|
-
if (!token) { console.error(`Not logged in. Run: ${CLI} login`); process.exit(1); }
|
|
98
|
-
|
|
99
|
-
const client = createClient({ instance: resolveInstanceUrl(opts.instance), token });
|
|
100
|
-
|
|
101
|
-
console.log(`Watching pod ${podId}... (Ctrl+C to stop)\n`);
|
|
102
|
-
|
|
103
|
-
const pollNew = createTailPoller({
|
|
104
|
-
fetchPage: (limit) => client.get(`/api/messages/${podId}`, { limit }),
|
|
105
|
-
filter: opts.filter,
|
|
106
|
-
print: (line) => console.log(line),
|
|
107
|
-
});
|
|
108
|
-
const poll = async () => {
|
|
109
|
-
try {
|
|
110
|
-
await pollNew();
|
|
111
|
-
} catch (err) {
|
|
112
|
-
console.error(`[poll error] ${err.message}`);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
await poll();
|
|
117
|
-
const interval = setInterval(poll, 3000);
|
|
118
|
-
process.on('SIGINT', () => { clearInterval(interval); process.exit(0); });
|
|
119
|
-
});
|
|
120
|
-
};
|
package/src/commands/task.js
DELETED
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* commonly task <subcommand>
|
|
3
|
-
*
|
|
4
|
-
* list — the pod's board, optionally narrowed by status, or to rows this identity holds
|
|
5
|
-
* create — a new row; --notes is a second request, because create does not take notes
|
|
6
|
-
* claim — take the lease on a row
|
|
7
|
-
* update — change a row's fields, and/or append a history note
|
|
8
|
-
* close — mark a row done, as its creator or its holder
|
|
9
|
-
*
|
|
10
|
-
* Identity and base URL come from ../lib/task.js. Every subcommand goes through
|
|
11
|
-
* connect() below, so none can grow its own fallback to the saved login. Flags are
|
|
12
|
-
* checked before the first request, so a refusal costs no network round trip.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { TOKEN_FILE_ENV } from '../lib/inbox.js';
|
|
16
|
-
import {
|
|
17
|
-
TASK_PRIORITIES,
|
|
18
|
-
TASK_STATUSES,
|
|
19
|
-
TASK_TOKEN_ENV,
|
|
20
|
-
TaskRefusal,
|
|
21
|
-
UPDATE_STATUSES,
|
|
22
|
-
checkPriority,
|
|
23
|
-
checkStatusFilter,
|
|
24
|
-
checkTaskId,
|
|
25
|
-
checkUpdateStatus,
|
|
26
|
-
createTaskClient,
|
|
27
|
-
describeFailure,
|
|
28
|
-
formatTaskLine,
|
|
29
|
-
requireText,
|
|
30
|
-
resolveTaskBaseUrl,
|
|
31
|
-
resolveTaskToken,
|
|
32
|
-
taskPath,
|
|
33
|
-
} from '../lib/task.js';
|
|
34
|
-
import { CLI } from '../lib/cli-hint.js';
|
|
35
|
-
|
|
36
|
-
export const TASK_TOKEN_HELP = `Agent token file (or ${TOKEN_FILE_ENV}, or ${TASK_TOKEN_ENV})`;
|
|
37
|
-
|
|
38
|
-
const connect = (opts, env, fetchImpl) => createTaskClient({
|
|
39
|
-
baseUrl: resolveTaskBaseUrl(opts, env),
|
|
40
|
-
token: resolveTaskToken(opts, env),
|
|
41
|
-
fetchImpl,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const podOf = (opts) => requireText(opts.pod, '--pod');
|
|
45
|
-
|
|
46
|
-
const report = (err, error) => {
|
|
47
|
-
error(err instanceof TaskRefusal ? `refusing: ${err.message}` : describeFailure(err));
|
|
48
|
-
return 1;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// A row goes out as JSON for agents, and as the line a person scans otherwise.
|
|
52
|
-
const emitRow = (opts, log, task) => {
|
|
53
|
-
log(opts.json ? JSON.stringify(task, null, 2) : formatTaskLine(task));
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export const runTaskList = async (opts = {}, deps = {}) => {
|
|
57
|
-
const {
|
|
58
|
-
env = process.env, fetchImpl = fetch, log = console.log, error = console.error,
|
|
59
|
-
} = deps;
|
|
60
|
-
try {
|
|
61
|
-
const pod = podOf(opts);
|
|
62
|
-
const status = opts.status === undefined ? undefined : checkStatusFilter(opts.status);
|
|
63
|
-
const { tasks } = await connect(opts, env, fetchImpl).get(taskPath(pod), { status });
|
|
64
|
-
// The server marks each row claimedByMe for this identity, so --mine needs no second request.
|
|
65
|
-
const rows = opts.mine ? tasks.filter((task) => task.claimedByMe) : tasks;
|
|
66
|
-
if (opts.json) {
|
|
67
|
-
log(JSON.stringify(rows, null, 2));
|
|
68
|
-
} else if (rows.length === 0) {
|
|
69
|
-
log('(no tasks match)');
|
|
70
|
-
} else {
|
|
71
|
-
for (const task of rows) log(formatTaskLine(task));
|
|
72
|
-
}
|
|
73
|
-
return 0;
|
|
74
|
-
} catch (err) {
|
|
75
|
-
return report(err, error);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export const runTaskCreate = async (opts = {}, deps = {}) => {
|
|
80
|
-
const {
|
|
81
|
-
env = process.env, fetchImpl = fetch, log = console.log, error = console.error,
|
|
82
|
-
} = deps;
|
|
83
|
-
try {
|
|
84
|
-
const pod = podOf(opts);
|
|
85
|
-
const body = { title: requireText(opts.title, '--title') };
|
|
86
|
-
if (opts.priority !== undefined) body.priority = checkPriority(opts.priority);
|
|
87
|
-
if (opts.assignee !== undefined) body.assignee = requireText(opts.assignee, '--assignee');
|
|
88
|
-
if (opts.dep !== undefined) body.dep = checkTaskId(opts.dep);
|
|
89
|
-
const notes = opts.notes === undefined ? undefined : requireText(opts.notes, '--notes');
|
|
90
|
-
const client = connect(opts, env, fetchImpl);
|
|
91
|
-
const { task } = await client.post(taskPath(pod), body);
|
|
92
|
-
if (notes === undefined) {
|
|
93
|
-
emitRow(opts, log, task);
|
|
94
|
-
return 0;
|
|
95
|
-
}
|
|
96
|
-
// The row exists once the create returns. If the notes fail, say so, so nobody
|
|
97
|
-
// retries the create and makes a twin.
|
|
98
|
-
try {
|
|
99
|
-
const { task: noted } = await client.patch(taskPath(pod, task.taskId), { notes });
|
|
100
|
-
emitRow(opts, log, noted);
|
|
101
|
-
return 0;
|
|
102
|
-
} catch (err) {
|
|
103
|
-
error(`Created ${task.taskId}, but --notes did not save. ${describeFailure(err)}`);
|
|
104
|
-
return 1;
|
|
105
|
-
}
|
|
106
|
-
} catch (err) {
|
|
107
|
-
return report(err, error);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
export const runTaskClaim = async (taskArg, opts = {}, deps = {}) => {
|
|
112
|
-
const {
|
|
113
|
-
env = process.env, fetchImpl = fetch, log = console.log, error = console.error,
|
|
114
|
-
} = deps;
|
|
115
|
-
try {
|
|
116
|
-
const pod = podOf(opts);
|
|
117
|
-
const taskId = checkTaskId(taskArg);
|
|
118
|
-
const body = opts.continueAnyway ? { continueAnyway: true } : {};
|
|
119
|
-
const { task } = await connect(opts, env, fetchImpl).post(`${taskPath(pod, taskId)}/claim`, body);
|
|
120
|
-
emitRow(opts, log, task);
|
|
121
|
-
return 0;
|
|
122
|
-
} catch (err) {
|
|
123
|
-
return report(err, error);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
export const runTaskUpdate = async (taskArg, opts = {}, deps = {}) => {
|
|
128
|
-
const {
|
|
129
|
-
env = process.env, fetchImpl = fetch, log = console.log, error = console.error,
|
|
130
|
-
} = deps;
|
|
131
|
-
try {
|
|
132
|
-
const pod = podOf(opts);
|
|
133
|
-
const taskId = checkTaskId(taskArg);
|
|
134
|
-
const fields = {};
|
|
135
|
-
if (opts.status !== undefined) fields.status = checkUpdateStatus(opts.status);
|
|
136
|
-
// An empty value unassigns. The server stores that as null, not as '', so the row
|
|
137
|
-
// reads as unassigned to the router.
|
|
138
|
-
if (opts.assignee !== undefined) fields.assignee = String(opts.assignee).trim();
|
|
139
|
-
if (opts.title !== undefined) fields.title = requireText(opts.title, '--title');
|
|
140
|
-
if (opts.priority !== undefined) fields.priority = checkPriority(opts.priority);
|
|
141
|
-
// "none" clears the dependency. Sending '' would store a dependency that names nothing.
|
|
142
|
-
if (opts.dep !== undefined) fields.dep = opts.dep === 'none' ? null : checkTaskId(opts.dep);
|
|
143
|
-
const note = opts.note === undefined ? undefined : requireText(opts.note, '--note');
|
|
144
|
-
if (Object.keys(fields).length === 0 && note === undefined) {
|
|
145
|
-
throw new TaskRefusal('nothing to change. Pass --status, --assignee, --title, --priority, --dep or --note.');
|
|
146
|
-
}
|
|
147
|
-
const client = connect(opts, env, fetchImpl);
|
|
148
|
-
const path = taskPath(pod, taskId);
|
|
149
|
-
let task = null;
|
|
150
|
-
if (Object.keys(fields).length > 0) ({ task } = await client.patch(path, fields));
|
|
151
|
-
if (note !== undefined) {
|
|
152
|
-
try {
|
|
153
|
-
const body = opts.continueAnyway ? { text: note, continueAnyway: true } : { text: note };
|
|
154
|
-
({ task } = await client.post(`${path}/updates`, body));
|
|
155
|
-
} catch (err) {
|
|
156
|
-
// With no field change before it, a failed note is the whole failure.
|
|
157
|
-
if (task === null) throw err;
|
|
158
|
-
error(`Updated ${taskId}, but --note did not post. ${describeFailure(err)}`);
|
|
159
|
-
return 1;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
emitRow(opts, log, task);
|
|
163
|
-
return 0;
|
|
164
|
-
} catch (err) {
|
|
165
|
-
return report(err, error);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
export const runTaskClose = async (taskArg, opts = {}, deps = {}) => {
|
|
170
|
-
const {
|
|
171
|
-
env = process.env, fetchImpl = fetch, log = console.log, error = console.error,
|
|
172
|
-
} = deps;
|
|
173
|
-
try {
|
|
174
|
-
const pod = podOf(opts);
|
|
175
|
-
const taskId = checkTaskId(taskArg);
|
|
176
|
-
const prUrl = opts.pr === undefined ? undefined : requireText(opts.pr, '--pr');
|
|
177
|
-
// No notes key here. /complete replaces the row's notes whenever one is sent.
|
|
178
|
-
const body = prUrl === undefined ? {} : { prUrl };
|
|
179
|
-
const { task } = await connect(opts, env, fetchImpl).post(`${taskPath(pod, taskId)}/complete`, body);
|
|
180
|
-
emitRow(opts, log, task);
|
|
181
|
-
return 0;
|
|
182
|
-
} catch (err) {
|
|
183
|
-
return report(err, error);
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
export const registerTask = (program) => {
|
|
188
|
-
const task = program
|
|
189
|
-
.command('task')
|
|
190
|
-
.description('Read and write the task board as an agent (list, create, claim, update, close)');
|
|
191
|
-
|
|
192
|
-
task.addHelpText('after', `
|
|
193
|
-
The board is read and written as the identity in a token file, and nowhere else:
|
|
194
|
-
|
|
195
|
-
$ ${CLI} task list --pod <podId> --token-file <path>
|
|
196
|
-
$ COMMONLY_AGENT_TOKEN=<token> ${CLI} task claim TASK-123 --pod <podId>
|
|
197
|
-
|
|
198
|
-
The identity is --token-file, then COMMONLY_TOKEN_FILE, then COMMONLY_AGENT_TOKEN.
|
|
199
|
-
The saved login and COMMONLY_TOKEN are never read: a write made under an
|
|
200
|
-
operator's login lands on the row under that operator's name.
|
|
201
|
-
|
|
202
|
-
Each verb does what it says. \`claim\` takes the lease. \`close\` stamps the
|
|
203
|
-
completion time and the PR. \`update --status\` sets only pending or blocked, so it
|
|
204
|
-
cannot claim or close a row. \`update --note\` appends a history line. The row's
|
|
205
|
-
notes are set by \`create --notes\` and are not editable here.
|
|
206
|
-
|
|
207
|
-
A claim or a note can refuse with unseen messages in the pod. Read them and retry,
|
|
208
|
-
or pass --continue-anyway to go ahead without reading them.
|
|
209
|
-
|
|
210
|
-
Examples:
|
|
211
|
-
$ ${CLI} task list --pod <podId> --status pending,claimed
|
|
212
|
-
$ ${CLI} task list --pod <podId> --mine --json
|
|
213
|
-
$ ${CLI} task create --pod <podId> --title "Fix the board" --priority P1 --notes "Repro in staging"
|
|
214
|
-
$ ${CLI} task claim TASK-123 --pod <podId>
|
|
215
|
-
$ ${CLI} task update TASK-123 --pod <podId> --note "PR is up"
|
|
216
|
-
$ ${CLI} task close TASK-123 --pod <podId> --pr https://github.com/Team-Space/commonly/pull/<n>
|
|
217
|
-
`);
|
|
218
|
-
|
|
219
|
-
task
|
|
220
|
-
.command('list')
|
|
221
|
-
.description("List a pod's task board")
|
|
222
|
-
.requiredOption('--pod <podId>', 'Pod whose board to read')
|
|
223
|
-
.option('--status <list>', `Only these statuses, comma-separated (${TASK_STATUSES.join('|')})`)
|
|
224
|
-
.option('--mine', 'Only rows this identity holds a claim on')
|
|
225
|
-
.option('--token-file <path>', TASK_TOKEN_HELP)
|
|
226
|
-
.option('--instance <url>', 'Target Space instance')
|
|
227
|
-
.option('--json', 'Emit the rows as JSON')
|
|
228
|
-
.action(async (opts) => { process.exit(await runTaskList(opts)); });
|
|
229
|
-
|
|
230
|
-
task
|
|
231
|
-
.command('create')
|
|
232
|
-
.description('Create a row, then set its --notes (create does not take notes)')
|
|
233
|
-
.requiredOption('--pod <podId>', 'Pod to create the row in')
|
|
234
|
-
.requiredOption('--title <text>', 'Row title')
|
|
235
|
-
.option('--notes <text>', 'Notes to set once the row exists')
|
|
236
|
-
.option('--priority <P>', `${TASK_PRIORITIES.join('|')} (default P2)`)
|
|
237
|
-
.option('--assignee <name>', 'Assign the row at creation')
|
|
238
|
-
.option('--dep <TASK-n>', 'Row this one waits on')
|
|
239
|
-
.option('--token-file <path>', TASK_TOKEN_HELP)
|
|
240
|
-
.option('--instance <url>', 'Target Space instance')
|
|
241
|
-
.option('--json', 'Emit the row as JSON')
|
|
242
|
-
.action(async (opts) => { process.exit(await runTaskCreate(opts)); });
|
|
243
|
-
|
|
244
|
-
task
|
|
245
|
-
.command('claim <taskId>')
|
|
246
|
-
.description('Take the lease on a row (TASK-n)')
|
|
247
|
-
.requiredOption('--pod <podId>', 'Pod the row is in')
|
|
248
|
-
.option('--continue-anyway', 'Claim even with unseen messages in the pod')
|
|
249
|
-
.option('--token-file <path>', TASK_TOKEN_HELP)
|
|
250
|
-
.option('--instance <url>', 'Target Space instance')
|
|
251
|
-
.option('--json', 'Emit the row as JSON')
|
|
252
|
-
.action(async (taskId, opts) => { process.exit(await runTaskClaim(taskId, opts)); });
|
|
253
|
-
|
|
254
|
-
task
|
|
255
|
-
.command('update <taskId>')
|
|
256
|
-
.description("Change a row's fields, and/or append a history note")
|
|
257
|
-
.requiredOption('--pod <podId>', 'Pod the row is in')
|
|
258
|
-
.option('--status <status>', `${UPDATE_STATUSES.join('|')} (claim and close have their own commands)`)
|
|
259
|
-
.option('--assignee <name>', 'Reassign the row; an empty value unassigns it')
|
|
260
|
-
.option('--title <text>', 'New title')
|
|
261
|
-
.option('--priority <P>', TASK_PRIORITIES.join('|'))
|
|
262
|
-
.option('--dep <TASK-n>', 'Row this one waits on; "none" clears it')
|
|
263
|
-
.option('--note <text>', 'Append a history note')
|
|
264
|
-
.option('--continue-anyway', 'Post the note even with unseen messages in the pod')
|
|
265
|
-
.option('--token-file <path>', TASK_TOKEN_HELP)
|
|
266
|
-
.option('--instance <url>', 'Target Space instance')
|
|
267
|
-
.option('--json', 'Emit the row as JSON')
|
|
268
|
-
.action(async (taskId, opts) => { process.exit(await runTaskUpdate(taskId, opts)); });
|
|
269
|
-
|
|
270
|
-
task
|
|
271
|
-
.command('close <taskId>')
|
|
272
|
-
.description('Mark a row done, as its creator or its holder')
|
|
273
|
-
.requiredOption('--pod <podId>', 'Pod the row is in')
|
|
274
|
-
.option('--pr <url>', 'Pull request that delivered the row')
|
|
275
|
-
.option('--token-file <path>', TASK_TOKEN_HELP)
|
|
276
|
-
.option('--instance <url>', 'Target Space instance')
|
|
277
|
-
.option('--json', 'Emit the row as JSON')
|
|
278
|
-
.action(async (taskId, opts) => { process.exit(await runTaskClose(taskId, opts)); });
|
|
279
|
-
};
|