@arbidocs/cli 0.3.63 → 0.3.64
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/CHANGELOG.md +29 -0
- package/SKILL.md +46 -5
- package/dist/index.js +1798 -433
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
var prompts = require('@inquirer/prompts');
|
|
4
5
|
var commander = require('commander');
|
|
5
6
|
var fs5 = require('fs');
|
|
6
7
|
var path5 = require('path');
|
|
7
|
-
var
|
|
8
|
+
var os2 = require('os');
|
|
8
9
|
var chalk2 = require('chalk');
|
|
9
10
|
var sdk = require('@arbidocs/sdk');
|
|
10
|
-
var prompts = require('@inquirer/prompts');
|
|
11
11
|
var child_process = require('child_process');
|
|
12
12
|
var client = require('@arbidocs/client');
|
|
13
13
|
var url = require('url');
|
|
14
|
+
var crypto$1 = require('crypto');
|
|
14
15
|
var module$1 = require('module');
|
|
15
16
|
|
|
16
17
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -18,11 +19,73 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
18
19
|
|
|
19
20
|
var fs5__default = /*#__PURE__*/_interopDefault(fs5);
|
|
20
21
|
var path5__default = /*#__PURE__*/_interopDefault(path5);
|
|
21
|
-
var
|
|
22
|
+
var os2__default = /*#__PURE__*/_interopDefault(os2);
|
|
22
23
|
var chalk2__default = /*#__PURE__*/_interopDefault(chalk2);
|
|
23
24
|
|
|
25
|
+
var __defProp = Object.defineProperty;
|
|
26
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
27
|
+
var __esm = (fn, res) => function __init() {
|
|
28
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
29
|
+
};
|
|
30
|
+
var __export = (target, all) => {
|
|
31
|
+
for (var name in all)
|
|
32
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/prompts.ts
|
|
36
|
+
var prompts_exports = {};
|
|
37
|
+
__export(prompts_exports, {
|
|
38
|
+
checkbox: () => prompts.checkbox,
|
|
39
|
+
confirm: () => prompts.confirm,
|
|
40
|
+
input: () => prompts.input,
|
|
41
|
+
password: () => prompts.password,
|
|
42
|
+
promptCheckbox: () => promptCheckbox,
|
|
43
|
+
promptConfirm: () => promptConfirm,
|
|
44
|
+
promptInput: () => promptInput,
|
|
45
|
+
promptPassword: () => promptPassword,
|
|
46
|
+
promptSearch: () => promptSearch,
|
|
47
|
+
promptSelect: () => promptSelect,
|
|
48
|
+
search: () => prompts.search,
|
|
49
|
+
select: () => prompts.select
|
|
50
|
+
});
|
|
51
|
+
async function promptSelect(message, choices) {
|
|
52
|
+
return prompts.select({ message, choices });
|
|
53
|
+
}
|
|
54
|
+
async function promptCheckbox(message, choices) {
|
|
55
|
+
return prompts.checkbox({ message, choices });
|
|
56
|
+
}
|
|
57
|
+
async function promptSearch(message, choices) {
|
|
58
|
+
return prompts.search({
|
|
59
|
+
message,
|
|
60
|
+
source: async (term) => {
|
|
61
|
+
if (!term) return choices;
|
|
62
|
+
const lower = term.toLowerCase();
|
|
63
|
+
return choices.filter((c) => c.name.toLowerCase().includes(lower));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function promptInput(message, required = true) {
|
|
68
|
+
return prompts.input({
|
|
69
|
+
message,
|
|
70
|
+
validate: required ? (v) => v.trim() ? true : "Required" : void 0
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async function promptPassword(message) {
|
|
74
|
+
return prompts.password({
|
|
75
|
+
message,
|
|
76
|
+
mask: "*",
|
|
77
|
+
validate: (v) => v ? true : "Required"
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function promptConfirm(message, defaultValue = true) {
|
|
81
|
+
return prompts.confirm({ message, default: defaultValue });
|
|
82
|
+
}
|
|
83
|
+
var init_prompts = __esm({
|
|
84
|
+
"src/prompts.ts"() {
|
|
85
|
+
}
|
|
86
|
+
});
|
|
24
87
|
function getCacheFile() {
|
|
25
|
-
const configDir = process.env.ARBI_CONFIG_DIR ?? path5__default.default.join(
|
|
88
|
+
const configDir = process.env.ARBI_CONFIG_DIR ?? path5__default.default.join(os2__default.default.homedir(), ".arbi");
|
|
26
89
|
return path5__default.default.join(configDir, "completions.json");
|
|
27
90
|
}
|
|
28
91
|
function ensureDir(filePath) {
|
|
@@ -40,6 +103,16 @@ function getCachedWorkspaceIds() {
|
|
|
40
103
|
return [];
|
|
41
104
|
}
|
|
42
105
|
}
|
|
106
|
+
function getCachedWorkspaceName(id) {
|
|
107
|
+
try {
|
|
108
|
+
const content = fs5__default.default.readFileSync(getCacheFile(), "utf-8");
|
|
109
|
+
const cache = JSON.parse(content);
|
|
110
|
+
const hit = cache.workspaces.find((w) => w.id === id);
|
|
111
|
+
return hit?.name?.trim() ? hit.name : null;
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
43
116
|
function updateCompletionCache(workspaces3) {
|
|
44
117
|
const cache = {
|
|
45
118
|
workspaces: workspaces3.filter((w) => w.external_id).map((w) => ({ id: w.external_id, name: w.name ?? "" })),
|
|
@@ -194,8 +267,8 @@ var ALIAS_LINE = 'alias A="arbi ask"';
|
|
|
194
267
|
var ALIAS_MARKER = "# arbi-cli alias";
|
|
195
268
|
function getShellRcPath() {
|
|
196
269
|
const shell = process.env.SHELL || "";
|
|
197
|
-
if (shell.includes("zsh")) return path5.join(
|
|
198
|
-
return path5.join(
|
|
270
|
+
if (shell.includes("zsh")) return path5.join(os2.homedir(), ".zshrc");
|
|
271
|
+
return path5.join(os2.homedir(), ".bashrc");
|
|
199
272
|
}
|
|
200
273
|
function isAliasInstalled(rcPath) {
|
|
201
274
|
if (!fs5.existsSync(rcPath)) return false;
|
|
@@ -203,7 +276,7 @@ function isAliasInstalled(rcPath) {
|
|
|
203
276
|
return content.includes(ALIAS_LINE) || content.includes(ALIAS_MARKER);
|
|
204
277
|
}
|
|
205
278
|
function registerConfigCommand(program2) {
|
|
206
|
-
const config = program2.command("config").description("
|
|
279
|
+
const config = program2.command("config").description("CLI configuration: set-url, show, notifications, verbose, watch, alias");
|
|
207
280
|
config.command("set-url <url>").description("Set the ARBI server URL").action((url) => {
|
|
208
281
|
try {
|
|
209
282
|
const parsed = new URL(url);
|
|
@@ -219,7 +292,17 @@ function registerConfigCommand(program2) {
|
|
|
219
292
|
config.command("show").description("Show current configuration").option("--json", "Output as JSON").action((opts) => {
|
|
220
293
|
const cfg = getConfig();
|
|
221
294
|
if (opts.json) {
|
|
222
|
-
|
|
295
|
+
const projection = cfg ? {
|
|
296
|
+
baseUrl: cfg.baseUrl ?? null,
|
|
297
|
+
deploymentDomain: cfg.deploymentDomain ?? null,
|
|
298
|
+
autoUpdate: cfg.autoUpdate ?? false,
|
|
299
|
+
verbose: cfg.verbose !== false,
|
|
300
|
+
watch: cfg.watch !== false,
|
|
301
|
+
notifications: cfg.notifications !== false,
|
|
302
|
+
orchestrator: cfg.orchestrator ?? null,
|
|
303
|
+
selectedWorkspaceId: cfg.selectedWorkspaceId ?? null
|
|
304
|
+
} : {};
|
|
305
|
+
console.log(JSON.stringify(projection, null, 2));
|
|
223
306
|
return;
|
|
224
307
|
}
|
|
225
308
|
if (!cfg) {
|
|
@@ -232,6 +315,7 @@ function registerConfigCommand(program2) {
|
|
|
232
315
|
label("Verbose:", cfg.verbose !== false ? "on" : "off");
|
|
233
316
|
label("Watch:", cfg.watch !== false ? "on" : "off");
|
|
234
317
|
label("Notifications:", cfg.notifications !== false ? "on" : "off");
|
|
318
|
+
if (cfg.orchestrator) label("Orchestrator:", cfg.orchestrator);
|
|
235
319
|
if (cfg.selectedWorkspaceId) {
|
|
236
320
|
label("Workspace:", cfg.selectedWorkspaceId);
|
|
237
321
|
}
|
|
@@ -275,7 +359,7 @@ function registerConfigCommand(program2) {
|
|
|
275
359
|
updateConfig({ watch: toggle === "on" });
|
|
276
360
|
success(`Watch: ${toggle}`);
|
|
277
361
|
});
|
|
278
|
-
config.command("alias").description('
|
|
362
|
+
config.command("alias").description('Append `alias A="arbi ask"` to your shell rc file (idempotent, reversible)').action(() => {
|
|
279
363
|
const rcPath = getShellRcPath();
|
|
280
364
|
if (isAliasInstalled(rcPath)) {
|
|
281
365
|
success(`Alias already set up in ${rcPath}`);
|
|
@@ -3255,7 +3339,7 @@ var waitForOthersClosedDelete = (databases, name, openDatabases, cb) => {
|
|
|
3255
3339
|
};
|
|
3256
3340
|
var deleteDatabase = (databases, connectionQueues, name, request, cb) => {
|
|
3257
3341
|
const deleteDBTask = () => {
|
|
3258
|
-
return new Promise((
|
|
3342
|
+
return new Promise((resolve5) => {
|
|
3259
3343
|
const db = databases.get(name);
|
|
3260
3344
|
const oldVersion = db !== void 0 ? db.version : 0;
|
|
3261
3345
|
const onComplete = (err) => {
|
|
@@ -3266,7 +3350,7 @@ var deleteDatabase = (databases, connectionQueues, name, request, cb) => {
|
|
|
3266
3350
|
cb(null, oldVersion);
|
|
3267
3351
|
}
|
|
3268
3352
|
} finally {
|
|
3269
|
-
|
|
3353
|
+
resolve5();
|
|
3270
3354
|
}
|
|
3271
3355
|
};
|
|
3272
3356
|
try {
|
|
@@ -3414,7 +3498,7 @@ var runVersionchangeTransaction = (connection, version, request, cb) => {
|
|
|
3414
3498
|
};
|
|
3415
3499
|
var openDatabase = (databases, connectionQueues, name, version, request, cb) => {
|
|
3416
3500
|
const openDBTask = () => {
|
|
3417
|
-
return new Promise((
|
|
3501
|
+
return new Promise((resolve5) => {
|
|
3418
3502
|
const onComplete = (err) => {
|
|
3419
3503
|
try {
|
|
3420
3504
|
if (err) {
|
|
@@ -3423,7 +3507,7 @@ var openDatabase = (databases, connectionQueues, name, version, request, cb) =>
|
|
|
3423
3507
|
cb(null, connection);
|
|
3424
3508
|
}
|
|
3425
3509
|
} finally {
|
|
3426
|
-
|
|
3510
|
+
resolve5();
|
|
3427
3511
|
}
|
|
3428
3512
|
};
|
|
3429
3513
|
let db = databases.get(name);
|
|
@@ -3572,39 +3656,8 @@ Object.defineProperties(globalVar, {
|
|
|
3572
3656
|
IDBTransaction: createPropertyDescriptor(FDBTransaction_default),
|
|
3573
3657
|
IDBVersionChangeEvent: createPropertyDescriptor(FDBVersionChangeEvent_default)
|
|
3574
3658
|
});
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
}
|
|
3578
|
-
async function promptCheckbox(message, choices) {
|
|
3579
|
-
return prompts.checkbox({ message, choices });
|
|
3580
|
-
}
|
|
3581
|
-
async function promptSearch(message, choices) {
|
|
3582
|
-
return prompts.search({
|
|
3583
|
-
message,
|
|
3584
|
-
source: async (term) => {
|
|
3585
|
-
if (!term) return choices;
|
|
3586
|
-
const lower = term.toLowerCase();
|
|
3587
|
-
return choices.filter((c) => c.name.toLowerCase().includes(lower));
|
|
3588
|
-
}
|
|
3589
|
-
});
|
|
3590
|
-
}
|
|
3591
|
-
async function promptInput(message, required = true) {
|
|
3592
|
-
return prompts.input({
|
|
3593
|
-
message,
|
|
3594
|
-
validate: required ? (v) => v.trim() ? true : "Required" : void 0
|
|
3595
|
-
});
|
|
3596
|
-
}
|
|
3597
|
-
async function promptPassword(message) {
|
|
3598
|
-
return prompts.password({
|
|
3599
|
-
message,
|
|
3600
|
-
mask: "*",
|
|
3601
|
-
validate: (v) => v ? true : "Required"
|
|
3602
|
-
});
|
|
3603
|
-
}
|
|
3604
|
-
async function promptConfirm(message, defaultValue = true) {
|
|
3605
|
-
return prompts.confirm({ message, default: defaultValue });
|
|
3606
|
-
}
|
|
3607
|
-
var CACHE_FILE = path5__default.default.join(os__default.default.homedir(), ".arbi", "version-cache.json");
|
|
3659
|
+
init_prompts();
|
|
3660
|
+
var CACHE_FILE = path5__default.default.join(os2__default.default.homedir(), ".arbi", "version-cache.json");
|
|
3608
3661
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3609
3662
|
function readCache() {
|
|
3610
3663
|
try {
|
|
@@ -3641,7 +3694,7 @@ function getLatestVersion(skipCache = false) {
|
|
|
3641
3694
|
}
|
|
3642
3695
|
}
|
|
3643
3696
|
function getCurrentVersion() {
|
|
3644
|
-
return "0.3.
|
|
3697
|
+
return "0.3.64";
|
|
3645
3698
|
}
|
|
3646
3699
|
function readChangelog(fromVersion, toVersion) {
|
|
3647
3700
|
try {
|
|
@@ -3694,38 +3747,61 @@ function showChangelog(fromVersion, toVersion) {
|
|
|
3694
3747
|
async function checkForUpdates(autoUpdate) {
|
|
3695
3748
|
try {
|
|
3696
3749
|
const latest = getLatestVersion();
|
|
3697
|
-
if (!latest || latest === "0.3.
|
|
3750
|
+
if (!latest || latest === "0.3.64") return;
|
|
3698
3751
|
if (autoUpdate) {
|
|
3699
3752
|
warn(`
|
|
3700
|
-
Your arbi version is out of date (${"0.3.
|
|
3753
|
+
Your arbi version is out of date (${"0.3.64"} \u2192 ${latest}). Updating...`);
|
|
3701
3754
|
child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
|
|
3702
|
-
showChangelog("0.3.
|
|
3755
|
+
showChangelog("0.3.64", latest);
|
|
3703
3756
|
console.log(`Updated to ${latest}.`);
|
|
3704
3757
|
} else {
|
|
3705
3758
|
warn(
|
|
3706
3759
|
`
|
|
3707
|
-
Your arbi version is out of date (${"0.3.
|
|
3760
|
+
Your arbi version is out of date (${"0.3.64"} \u2192 ${latest}).
|
|
3708
3761
|
Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
|
|
3709
3762
|
);
|
|
3710
3763
|
}
|
|
3711
3764
|
} catch {
|
|
3712
3765
|
}
|
|
3713
3766
|
}
|
|
3767
|
+
var NAG_FILE = path5__default.default.join(os2__default.default.homedir(), ".arbi", "last-update-hint.json");
|
|
3768
|
+
var NAG_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
3769
|
+
function shouldShowNag(latest) {
|
|
3770
|
+
if (process.env.ARBI_JSON === "1") return false;
|
|
3771
|
+
if (process.argv.includes("--json")) return false;
|
|
3772
|
+
try {
|
|
3773
|
+
const data = JSON.parse(fs5__default.default.readFileSync(NAG_FILE, "utf8"));
|
|
3774
|
+
if (data.latest === latest && typeof data.shownAt === "number" && Date.now() - data.shownAt < NAG_INTERVAL_MS) {
|
|
3775
|
+
return false;
|
|
3776
|
+
}
|
|
3777
|
+
} catch {
|
|
3778
|
+
}
|
|
3779
|
+
return true;
|
|
3780
|
+
}
|
|
3781
|
+
function markNagShown(latest) {
|
|
3782
|
+
try {
|
|
3783
|
+
const dir = path5__default.default.dirname(NAG_FILE);
|
|
3784
|
+
if (!fs5__default.default.existsSync(dir)) fs5__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3785
|
+
fs5__default.default.writeFileSync(NAG_FILE, JSON.stringify({ latest, shownAt: Date.now() }) + "\n");
|
|
3786
|
+
} catch {
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3714
3789
|
function hintUpdateOnError() {
|
|
3715
3790
|
try {
|
|
3716
3791
|
const cached = readCache();
|
|
3717
|
-
if (cached
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
)
|
|
3721
|
-
|
|
3792
|
+
if (!cached || cached.latest === "0.3.64") return;
|
|
3793
|
+
if (!shouldShowNag(cached.latest)) return;
|
|
3794
|
+
warn(
|
|
3795
|
+
`Your arbi version is out of date (${"0.3.64"} \u2192 ${cached.latest}). Run "arbi update".`
|
|
3796
|
+
);
|
|
3797
|
+
markNagShown(cached.latest);
|
|
3722
3798
|
} catch {
|
|
3723
3799
|
}
|
|
3724
3800
|
}
|
|
3725
3801
|
var MAX_TASKS = 50;
|
|
3726
3802
|
var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3727
3803
|
function getTasksFile() {
|
|
3728
|
-
const configDir = process.env.ARBI_CONFIG_DIR ?? path5__default.default.join(
|
|
3804
|
+
const configDir = process.env.ARBI_CONFIG_DIR ?? path5__default.default.join(os2__default.default.homedir(), ".arbi");
|
|
3729
3805
|
return path5__default.default.join(configDir, "tasks.json");
|
|
3730
3806
|
}
|
|
3731
3807
|
function ensureDir2(filePath) {
|
|
@@ -3858,14 +3934,46 @@ function formatCliError(err) {
|
|
|
3858
3934
|
if (err instanceof sdk.ArbiApiError && err.apiError && typeof err.apiError === "object") {
|
|
3859
3935
|
const base = err.message;
|
|
3860
3936
|
const apiErr = err.apiError;
|
|
3861
|
-
const detail = apiErr.detail ?? apiErr.message ?? apiErr.error;
|
|
3862
|
-
if (
|
|
3937
|
+
const detail = stringifyApiDetail(apiErr.detail ?? apiErr.message ?? apiErr.error ?? apiErr);
|
|
3938
|
+
if (detail && !base.includes(detail)) {
|
|
3863
3939
|
return `${base} \u2014 ${detail}`;
|
|
3864
3940
|
}
|
|
3865
3941
|
return base;
|
|
3866
3942
|
}
|
|
3867
3943
|
return sdk.getErrorMessage(err);
|
|
3868
3944
|
}
|
|
3945
|
+
function stringifyApiDetail(value) {
|
|
3946
|
+
if (!value) return "";
|
|
3947
|
+
if (typeof value === "string") return value;
|
|
3948
|
+
if (Array.isArray(value)) {
|
|
3949
|
+
const parts = value.map((v) => {
|
|
3950
|
+
if (typeof v === "string") return v;
|
|
3951
|
+
if (v && typeof v === "object") {
|
|
3952
|
+
const rec = v;
|
|
3953
|
+
const loc = Array.isArray(rec.loc) ? rec.loc.join(".") : void 0;
|
|
3954
|
+
const msg = typeof rec.msg === "string" ? rec.msg : void 0;
|
|
3955
|
+
if (loc && msg) return `${loc}: ${msg}`;
|
|
3956
|
+
return stringifyApiDetail(v);
|
|
3957
|
+
}
|
|
3958
|
+
return "";
|
|
3959
|
+
}).filter(Boolean);
|
|
3960
|
+
return parts.join("; ");
|
|
3961
|
+
}
|
|
3962
|
+
if (typeof value === "object") {
|
|
3963
|
+
const rec = value;
|
|
3964
|
+
if (typeof rec.detail === "string") return rec.detail;
|
|
3965
|
+
if (Array.isArray(rec.detail)) return stringifyApiDetail(rec.detail);
|
|
3966
|
+
if (typeof rec.message === "string") return rec.message;
|
|
3967
|
+
if (typeof rec.error === "string") return rec.error;
|
|
3968
|
+
try {
|
|
3969
|
+
const s = JSON.stringify(value);
|
|
3970
|
+
return s === "{}" ? "" : s;
|
|
3971
|
+
} catch {
|
|
3972
|
+
return "";
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
return String(value);
|
|
3976
|
+
}
|
|
3869
3977
|
function runAction(fn) {
|
|
3870
3978
|
return async () => {
|
|
3871
3979
|
try {
|
|
@@ -3918,13 +4026,19 @@ async function resolveDmCrypto() {
|
|
|
3918
4026
|
const crypto2 = sdk.dm.createDmCryptoContext(arbi, loginResult.signingPrivateKey, userExtId);
|
|
3919
4027
|
return { ...authCtx, crypto: crypto2 };
|
|
3920
4028
|
}
|
|
4029
|
+
function truncate(value, width) {
|
|
4030
|
+
if (width <= 0) return "";
|
|
4031
|
+
if (value.length <= width) return value;
|
|
4032
|
+
if (width === 1) return "\u2026";
|
|
4033
|
+
return value.slice(0, width - 1) + "\u2026";
|
|
4034
|
+
}
|
|
3921
4035
|
function printTable(columns, rows) {
|
|
3922
4036
|
console.log(chalk2__default.default.bold(columns.map((c) => c.header.padEnd(c.width)).join("")));
|
|
3923
4037
|
for (const row of rows) {
|
|
3924
4038
|
console.log(
|
|
3925
4039
|
columns.map((c) => {
|
|
3926
4040
|
const val = c.value(row) ?? "";
|
|
3927
|
-
return val
|
|
4041
|
+
return truncate(val, c.width - 1).padEnd(c.width);
|
|
3928
4042
|
}).join("")
|
|
3929
4043
|
);
|
|
3930
4044
|
}
|
|
@@ -3937,6 +4051,91 @@ function parseJsonArg(input2, example) {
|
|
|
3937
4051
|
process.exit(1);
|
|
3938
4052
|
}
|
|
3939
4053
|
}
|
|
4054
|
+
function isInteractive() {
|
|
4055
|
+
return Boolean(process.stdin.isTTY);
|
|
4056
|
+
}
|
|
4057
|
+
function requireInteractive(hint) {
|
|
4058
|
+
if (isInteractive()) return;
|
|
4059
|
+
error(`Cannot prompt: not running in a terminal. ${hint}`);
|
|
4060
|
+
process.exit(1);
|
|
4061
|
+
}
|
|
4062
|
+
function printJson(value) {
|
|
4063
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
4064
|
+
}
|
|
4065
|
+
function nearestMatch(input2, candidates) {
|
|
4066
|
+
if (!input2 || candidates.length === 0) return null;
|
|
4067
|
+
const lower = input2.toLowerCase();
|
|
4068
|
+
const prefix = candidates.find((c) => c.startsWith(lower));
|
|
4069
|
+
if (prefix) return prefix;
|
|
4070
|
+
let best = null;
|
|
4071
|
+
for (const c of candidates) {
|
|
4072
|
+
let score = 0;
|
|
4073
|
+
for (let i = 0; i < Math.min(c.length, lower.length); i++) {
|
|
4074
|
+
if (c[i] === lower[i]) score++;
|
|
4075
|
+
else break;
|
|
4076
|
+
}
|
|
4077
|
+
if (!best || score > best.score) best = { name: c, score };
|
|
4078
|
+
}
|
|
4079
|
+
return best && best.score >= 1 ? best.name : null;
|
|
4080
|
+
}
|
|
4081
|
+
function suggestSubcommandAndExit(parentName, attempted, candidates) {
|
|
4082
|
+
const suggestion = nearestMatch(attempted, candidates);
|
|
4083
|
+
const hint = suggestion ? ` (Did you mean ${suggestion}?)` : "";
|
|
4084
|
+
error(
|
|
4085
|
+
`unknown subcommand '${attempted}' for '${parentName}'${hint}
|
|
4086
|
+
Available: ${candidates.join(", ")}`
|
|
4087
|
+
);
|
|
4088
|
+
process.exit(1);
|
|
4089
|
+
}
|
|
4090
|
+
function emitListOrTable(data, opts, emptyMessage) {
|
|
4091
|
+
if (opts.json) {
|
|
4092
|
+
printJson(data);
|
|
4093
|
+
return false;
|
|
4094
|
+
}
|
|
4095
|
+
if (data.length === 0) {
|
|
4096
|
+
process.stderr.write(`${emptyMessage}
|
|
4097
|
+
`);
|
|
4098
|
+
return false;
|
|
4099
|
+
}
|
|
4100
|
+
return true;
|
|
4101
|
+
}
|
|
4102
|
+
function resolveByIdOrName(list, selector) {
|
|
4103
|
+
const byId = list.find((w) => w.external_id === selector);
|
|
4104
|
+
if (byId) return { ok: true, item: byId, id: byId.external_id };
|
|
4105
|
+
const lower = selector.toLowerCase();
|
|
4106
|
+
const byName = list.filter((w) => (w.name ?? "").toLowerCase() === lower);
|
|
4107
|
+
if (byName.length === 1) return { ok: true, item: byName[0], id: byName[0].external_id };
|
|
4108
|
+
if (byName.length > 1) return { ok: false, reason: "ambiguous", matches: byName };
|
|
4109
|
+
return { ok: false, reason: "not-found", matches: [] };
|
|
4110
|
+
}
|
|
4111
|
+
function failResolveAndExit(thing, selector, res) {
|
|
4112
|
+
if (res.reason === "ambiguous") {
|
|
4113
|
+
error(
|
|
4114
|
+
`Ambiguous ${thing} "${selector}" \u2014 matches ${res.matches.map((m) => m.external_id).join(", ")}. Use the ID.`
|
|
4115
|
+
);
|
|
4116
|
+
} else {
|
|
4117
|
+
error(`${thing} "${selector}" not found.`);
|
|
4118
|
+
}
|
|
4119
|
+
process.exit(3);
|
|
4120
|
+
}
|
|
4121
|
+
function dryRun(verb, targets) {
|
|
4122
|
+
const list = Array.isArray(targets) ? targets : [targets];
|
|
4123
|
+
process.stdout.write(`[dry-run] Would ${verb}:
|
|
4124
|
+
`);
|
|
4125
|
+
for (const t of list) process.stdout.write(` - ${t}
|
|
4126
|
+
`);
|
|
4127
|
+
process.stdout.write("[dry-run] (no changes made; drop --dry-run to execute)\n");
|
|
4128
|
+
}
|
|
4129
|
+
async function pickFromList(items, toChoice, options) {
|
|
4130
|
+
if (items.length === 0) {
|
|
4131
|
+
process.stderr.write(`${options.emptyMessage}
|
|
4132
|
+
`);
|
|
4133
|
+
process.exit(0);
|
|
4134
|
+
}
|
|
4135
|
+
requireInteractive(options.nonTtyHint);
|
|
4136
|
+
const { promptSelect: promptSelect2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
|
|
4137
|
+
return promptSelect2(options.message, items.map(toChoice));
|
|
4138
|
+
}
|
|
3940
4139
|
var AGENT_BACKENDS = ["claude", "openclaw"];
|
|
3941
4140
|
var AGENT_MIN_VERSIONS = {
|
|
3942
4141
|
claude: {
|
|
@@ -3995,7 +4194,7 @@ function installSkill(backend) {
|
|
|
3995
4194
|
return;
|
|
3996
4195
|
}
|
|
3997
4196
|
if (backend === "claude") {
|
|
3998
|
-
const dir = path5.join(
|
|
4197
|
+
const dir = path5.join(os2.homedir(), ".claude", "commands", "arbi");
|
|
3999
4198
|
const target = path5.join(dir, "SKILL.md");
|
|
4000
4199
|
try {
|
|
4001
4200
|
fs5.mkdirSync(dir, { recursive: true });
|
|
@@ -4006,7 +4205,7 @@ function installSkill(backend) {
|
|
|
4006
4205
|
} catch {
|
|
4007
4206
|
}
|
|
4008
4207
|
} else if (backend === "openclaw") {
|
|
4009
|
-
const workspace = path5.join(
|
|
4208
|
+
const workspace = path5.join(os2.homedir(), ".arbi", "openclaw-workspace");
|
|
4010
4209
|
const bootstrap = path5.join(workspace, "BOOTSTRAP.md");
|
|
4011
4210
|
try {
|
|
4012
4211
|
fs5.mkdirSync(workspace, { recursive: true });
|
|
@@ -4091,7 +4290,9 @@ async function startListening(agentName, config) {
|
|
|
4091
4290
|
});
|
|
4092
4291
|
}
|
|
4093
4292
|
function registerListenCommand(program2) {
|
|
4094
|
-
program2.command("listen").description(
|
|
4293
|
+
program2.command("listen").description(
|
|
4294
|
+
"Start DM listener (foreground; runs until Ctrl-C \u2014 use `& disown` or systemd for background)"
|
|
4295
|
+
).option("--agent <name>", `Agent backend: ${AGENT_BACKENDS.join(", ")}`).action(
|
|
4095
4296
|
(opts) => (async () => {
|
|
4096
4297
|
const config = resolveConfig();
|
|
4097
4298
|
await setupAgent(opts.agent, config);
|
|
@@ -4118,7 +4319,8 @@ function registerLoginCommand(program2) {
|
|
|
4118
4319
|
}
|
|
4119
4320
|
const passwordFromFlagOrEnv = opts.password || process.env.ARBI_PASSWORD;
|
|
4120
4321
|
if (!isTty && !passwordFromFlagOrEnv && !opts.signingKey) {
|
|
4121
|
-
const
|
|
4322
|
+
const why = opts.sso ? "\nNote: --sso still requires --password \u2014 the master password is the E2E key seed,\nthe SSO token only proves identity. ARBI has no passwordless mode." : "";
|
|
4323
|
+
const msg = "Password is required when stdin is not a TTY. Use --password <password> or set ARBI_PASSWORD." + why;
|
|
4122
4324
|
if (opts.json) console.log(JSON.stringify({ ok: false, error: msg }));
|
|
4123
4325
|
else error(msg);
|
|
4124
4326
|
process.exit(1);
|
|
@@ -4221,6 +4423,7 @@ Open this URL in your browser:
|
|
|
4221
4423
|
})()
|
|
4222
4424
|
);
|
|
4223
4425
|
}
|
|
4426
|
+
init_prompts();
|
|
4224
4427
|
var CENTRAL_API_URL = "https://central.arbi.work";
|
|
4225
4428
|
var RegisterHintError = class extends Error {
|
|
4226
4429
|
constructor(message, kind) {
|
|
@@ -4360,6 +4563,17 @@ async function nonInteractiveRegister(config, opts) {
|
|
|
4360
4563
|
const password2 = opts.password || process.env.ARBI_PASSWORD;
|
|
4361
4564
|
const supportApiKey = process.env.SUPPORT_API_KEY;
|
|
4362
4565
|
if (!email) {
|
|
4566
|
+
if (opts.json) {
|
|
4567
|
+
console.log(
|
|
4568
|
+
JSON.stringify({
|
|
4569
|
+
ok: false,
|
|
4570
|
+
stage: "preflight",
|
|
4571
|
+
error: "Email required",
|
|
4572
|
+
hint: "Use --email <email> or set ARBI_EMAIL"
|
|
4573
|
+
})
|
|
4574
|
+
);
|
|
4575
|
+
process.exit(1);
|
|
4576
|
+
}
|
|
4363
4577
|
error("Email required. Use --email <email> or set ARBI_EMAIL");
|
|
4364
4578
|
process.exit(1);
|
|
4365
4579
|
}
|
|
@@ -4368,6 +4582,17 @@ async function nonInteractiveRegister(config, opts) {
|
|
|
4368
4582
|
if (!opts.json) console.log(`Using email: ${email}`);
|
|
4369
4583
|
}
|
|
4370
4584
|
if (!password2) {
|
|
4585
|
+
if (opts.json) {
|
|
4586
|
+
console.log(
|
|
4587
|
+
JSON.stringify({
|
|
4588
|
+
ok: false,
|
|
4589
|
+
stage: "preflight",
|
|
4590
|
+
error: "Password required",
|
|
4591
|
+
hint: "Use --password <password> or set ARBI_PASSWORD"
|
|
4592
|
+
})
|
|
4593
|
+
);
|
|
4594
|
+
process.exit(1);
|
|
4595
|
+
}
|
|
4371
4596
|
error("Password required. Use --password <password> or set ARBI_PASSWORD");
|
|
4372
4597
|
process.exit(1);
|
|
4373
4598
|
}
|
|
@@ -4388,6 +4613,17 @@ async function nonInteractiveRegister(config, opts) {
|
|
|
4388
4613
|
verificationCode = opts.verificationCode;
|
|
4389
4614
|
} else {
|
|
4390
4615
|
if (!supportApiKey) {
|
|
4616
|
+
if (opts.json) {
|
|
4617
|
+
console.log(
|
|
4618
|
+
JSON.stringify({
|
|
4619
|
+
ok: false,
|
|
4620
|
+
stage: "preflight",
|
|
4621
|
+
error: "Verification code required",
|
|
4622
|
+
hint: "Use --verification-code <code> or set SUPPORT_API_KEY for CI mode"
|
|
4623
|
+
})
|
|
4624
|
+
);
|
|
4625
|
+
process.exit(1);
|
|
4626
|
+
}
|
|
4391
4627
|
error(
|
|
4392
4628
|
"Verification code required. Use --verification-code <code> or set SUPPORT_API_KEY for CI mode"
|
|
4393
4629
|
);
|
|
@@ -4532,6 +4768,8 @@ function registerStatusCommand(program2) {
|
|
|
4532
4768
|
program2.command("status").description("Show current configuration and login status").option("--json", "Output as JSON for scripting").action((opts) => {
|
|
4533
4769
|
const config = getConfig();
|
|
4534
4770
|
const creds = getCredentials();
|
|
4771
|
+
const wsId = config?.selectedWorkspaceId ?? null;
|
|
4772
|
+
const wsName = wsId ? getCachedWorkspaceName(wsId) : null;
|
|
4535
4773
|
if (opts.json) {
|
|
4536
4774
|
console.log(
|
|
4537
4775
|
JSON.stringify(
|
|
@@ -4540,7 +4778,8 @@ function registerStatusCommand(program2) {
|
|
|
4540
4778
|
server: config?.baseUrl ?? null,
|
|
4541
4779
|
logged_in: !!creds,
|
|
4542
4780
|
user: creds?.email ?? null,
|
|
4543
|
-
workspace:
|
|
4781
|
+
workspace: wsId,
|
|
4782
|
+
workspace_name: wsName
|
|
4544
4783
|
},
|
|
4545
4784
|
null,
|
|
4546
4785
|
2
|
|
@@ -4558,13 +4797,14 @@ function registerStatusCommand(program2) {
|
|
|
4558
4797
|
} else {
|
|
4559
4798
|
label("User:", "(not logged in)");
|
|
4560
4799
|
}
|
|
4561
|
-
if (
|
|
4562
|
-
label("Workspace:",
|
|
4800
|
+
if (wsId) {
|
|
4801
|
+
label("Workspace:", wsName ? `${wsName} (${wsId})` : wsId);
|
|
4563
4802
|
} else {
|
|
4564
4803
|
label("Workspace:", "(none selected)");
|
|
4565
4804
|
}
|
|
4566
4805
|
});
|
|
4567
4806
|
}
|
|
4807
|
+
init_prompts();
|
|
4568
4808
|
function resolveWorkspaceSelector(list, selector) {
|
|
4569
4809
|
const byId = list.find((w) => w.external_id === selector);
|
|
4570
4810
|
if (byId) return { ok: true, id: byId.external_id, ws: byId };
|
|
@@ -4587,35 +4827,55 @@ function printResolveError(list, selector, res) {
|
|
|
4587
4827
|
for (const w of list) error(` ${w.external_id} ${w.name}`);
|
|
4588
4828
|
}
|
|
4589
4829
|
function registerWorkspacesCommand(program2) {
|
|
4590
|
-
program2.command("workspaces").description("List workspaces").option("--json", "Output as JSON").option("--ids", "Output only workspace IDs (one per line)").
|
|
4830
|
+
program2.command("workspaces").description("List workspaces").option("--json", "Output as JSON").option("--ids", "Output only workspace IDs (one per line)").option(
|
|
4831
|
+
"--mine",
|
|
4832
|
+
"Only workspaces where you have a membership role (hide deployment-wide common ones)"
|
|
4833
|
+
).action(
|
|
4591
4834
|
(opts) => runAction(async () => {
|
|
4592
4835
|
const { arbi } = await resolveAuth();
|
|
4593
|
-
const
|
|
4594
|
-
updateCompletionCache(
|
|
4836
|
+
const allWorkspaces = await sdk.workspaces.listWorkspaces(arbi);
|
|
4837
|
+
updateCompletionCache(allWorkspaces);
|
|
4838
|
+
const data = opts.mine ? allWorkspaces.filter((w) => w.users?.length > 0) : allWorkspaces;
|
|
4595
4839
|
if (opts.ids) {
|
|
4596
4840
|
for (const w of data) console.log(w.external_id);
|
|
4597
4841
|
return;
|
|
4598
4842
|
}
|
|
4599
4843
|
if (opts.json) {
|
|
4600
|
-
const
|
|
4601
|
-
const out = data.map((w) =>
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4844
|
+
const selectedId2 = getConfig()?.selectedWorkspaceId ?? null;
|
|
4845
|
+
const out = data.map((w) => {
|
|
4846
|
+
const role = w.users?.[0]?.role ?? null;
|
|
4847
|
+
return {
|
|
4848
|
+
id: w.external_id,
|
|
4849
|
+
name: w.name,
|
|
4850
|
+
docs: w.shared_document_count + w.private_document_count,
|
|
4851
|
+
role,
|
|
4852
|
+
is_common: role === null,
|
|
4853
|
+
is_selected: w.external_id === selectedId2
|
|
4854
|
+
};
|
|
4855
|
+
});
|
|
4608
4856
|
console.log(JSON.stringify(out, null, 2));
|
|
4609
4857
|
return;
|
|
4610
4858
|
}
|
|
4611
4859
|
if (data.length === 0) {
|
|
4612
|
-
|
|
4860
|
+
process.stderr.write(
|
|
4861
|
+
opts.mine ? "No workspaces you are a member of. (Drop --mine to see deployment-wide ones.)\n" : "No workspaces found.\n"
|
|
4862
|
+
);
|
|
4613
4863
|
return;
|
|
4614
4864
|
}
|
|
4865
|
+
const selectedId = getConfig()?.selectedWorkspaceId ?? null;
|
|
4615
4866
|
printTable(
|
|
4616
4867
|
[
|
|
4617
4868
|
{ header: "ID", width: 24, value: (r) => r.external_id },
|
|
4618
|
-
{
|
|
4869
|
+
{
|
|
4870
|
+
header: "NAME",
|
|
4871
|
+
width: 30,
|
|
4872
|
+
// Asterisk-mark the active workspace so a glance at the table
|
|
4873
|
+
// tells you which one `arbi docs / ask / upload` will hit.
|
|
4874
|
+
value: (r) => {
|
|
4875
|
+
const name = r.name ?? "";
|
|
4876
|
+
return r.external_id === selectedId ? `* ${name}` : ` ${name}`;
|
|
4877
|
+
}
|
|
4878
|
+
},
|
|
4619
4879
|
{
|
|
4620
4880
|
header: "DOCS",
|
|
4621
4881
|
width: 6,
|
|
@@ -4623,8 +4883,11 @@ function registerWorkspacesCommand(program2) {
|
|
|
4623
4883
|
},
|
|
4624
4884
|
{
|
|
4625
4885
|
header: "ROLE",
|
|
4626
|
-
width:
|
|
4627
|
-
|
|
4886
|
+
width: 12,
|
|
4887
|
+
// `common` (italics dimmed) for memberless rows the caller can
|
|
4888
|
+
// see only because it's a deployment-wide workspace. The blank
|
|
4889
|
+
// ROLE that used to render here read like missing data.
|
|
4890
|
+
value: (r) => r.users?.[0]?.role ?? "common"
|
|
4628
4891
|
}
|
|
4629
4892
|
],
|
|
4630
4893
|
data
|
|
@@ -4699,7 +4962,7 @@ function registerWorkspacesCommand(program2) {
|
|
|
4699
4962
|
success(`Selected: ${ws.name} (${ref(selectedId)})`);
|
|
4700
4963
|
})()
|
|
4701
4964
|
);
|
|
4702
|
-
workspace.command("create <name>").description("Create a new workspace").option("-d, --description <text>", "Workspace description").option("--public", "Make workspace public", false).option("--select", "Set the new workspace as the active selection", false).
|
|
4965
|
+
workspace.command("create <name>").description("Create a new workspace").option("-d, --description <text>", "Workspace description").option("--public", "Make workspace public", false).option("--select", "Set the new workspace as the active selection", false).action(
|
|
4703
4966
|
(name, opts) => runAction(async () => {
|
|
4704
4967
|
const { arbi, loginResult } = await resolveAuth();
|
|
4705
4968
|
const userProjects = await sdk.projects.listProjects(arbi);
|
|
@@ -4718,25 +4981,11 @@ function registerWorkspacesCommand(program2) {
|
|
|
4718
4981
|
updateConfig({ selectedWorkspaceId: data.external_id });
|
|
4719
4982
|
clearChatSession();
|
|
4720
4983
|
}
|
|
4721
|
-
if (opts.json) {
|
|
4722
|
-
console.log(
|
|
4723
|
-
JSON.stringify(
|
|
4724
|
-
{
|
|
4725
|
-
id: data.external_id,
|
|
4726
|
-
name: data.name,
|
|
4727
|
-
selected: opts.select ?? false
|
|
4728
|
-
},
|
|
4729
|
-
null,
|
|
4730
|
-
2
|
|
4731
|
-
)
|
|
4732
|
-
);
|
|
4733
|
-
return;
|
|
4734
|
-
}
|
|
4735
4984
|
success(`Created: ${data.name} (${ref(data.external_id)})`);
|
|
4736
4985
|
if (opts.select) success(`Selected: ${data.name} (${ref(data.external_id)})`);
|
|
4737
4986
|
})()
|
|
4738
4987
|
);
|
|
4739
|
-
workspace.command("delete [id-or-name]").description("Delete a workspace (defaults to selected workspace)").option("-y, --yes", "Skip confirmation prompt (required in non-interactive shells)", false).option("--
|
|
4988
|
+
workspace.command("delete [id-or-name]").description("Delete a workspace (defaults to selected workspace)").option("-y, --yes", "Skip confirmation prompt (required in non-interactive shells)", false).option("--dry-run", "Preview which workspace would be deleted (no SDK call)").action(
|
|
4740
4989
|
(idOrName, opts) => runAction(async () => {
|
|
4741
4990
|
const { arbi } = await resolveAuth();
|
|
4742
4991
|
const config = getConfig();
|
|
@@ -4758,9 +5007,14 @@ function registerWorkspacesCommand(program2) {
|
|
|
4758
5007
|
process.exit(1);
|
|
4759
5008
|
}
|
|
4760
5009
|
}
|
|
4761
|
-
|
|
5010
|
+
if (opts.dryRun) {
|
|
5011
|
+
const label2 = targetName ? `${targetId} ("${targetName}")` : targetId;
|
|
5012
|
+
dryRun("delete workspace (along with all its documents, conversations, and tags)", label2);
|
|
5013
|
+
return;
|
|
5014
|
+
}
|
|
5015
|
+
const isInteractive2 = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
4762
5016
|
if (!opts.yes) {
|
|
4763
|
-
if (!
|
|
5017
|
+
if (!isInteractive2) {
|
|
4764
5018
|
error(
|
|
4765
5019
|
`Refusing to delete ${targetId} without confirmation. Re-run with --yes (non-interactive shell).`
|
|
4766
5020
|
);
|
|
@@ -4784,22 +5038,42 @@ function registerWorkspacesCommand(program2) {
|
|
|
4784
5038
|
if (session.workspaceId === targetId) {
|
|
4785
5039
|
clearChatSession();
|
|
4786
5040
|
}
|
|
4787
|
-
if (opts.json) {
|
|
4788
|
-
console.log(JSON.stringify({ id: targetId, deleted: true }, null, 2));
|
|
4789
|
-
return;
|
|
4790
|
-
}
|
|
4791
5041
|
success(`Deleted workspace ${targetId}`);
|
|
4792
5042
|
})()
|
|
4793
5043
|
);
|
|
4794
|
-
workspace.command("
|
|
5044
|
+
workspace.command("rename <name>").description("Rename the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
|
|
5045
|
+
(name, opts) => runAction(async () => {
|
|
5046
|
+
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
5047
|
+
const data = await sdk.workspaces.updateWorkspace(arbi, { name });
|
|
5048
|
+
success(`Renamed: ${data.name} (${ref(data.external_id)})`);
|
|
5049
|
+
})()
|
|
5050
|
+
);
|
|
5051
|
+
workspace.command("describe <description>").description("Update the workspace's description").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
|
|
5052
|
+
(description, opts) => runAction(async () => {
|
|
5053
|
+
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
5054
|
+
const data = await sdk.workspaces.updateWorkspace(arbi, {
|
|
5055
|
+
description
|
|
5056
|
+
});
|
|
5057
|
+
success(`Updated description: ${data.name} (${ref(data.external_id)})`);
|
|
5058
|
+
})()
|
|
5059
|
+
);
|
|
5060
|
+
workspace.command("public <on-or-off>").description("Toggle workspace visibility (on = public, off = private)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
|
|
5061
|
+
(onOff, opts) => runAction(async () => {
|
|
5062
|
+
const isPublic = onOff === "on" || onOff === "true" || onOff === "public";
|
|
5063
|
+
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
5064
|
+
const data = await sdk.workspaces.updateWorkspace(arbi, {
|
|
5065
|
+
is_public: isPublic
|
|
5066
|
+
});
|
|
5067
|
+
success(
|
|
5068
|
+
`Visibility: ${isPublic ? "public" : "private"} \u2014 ${data.name} (${ref(data.external_id)})`
|
|
5069
|
+
);
|
|
5070
|
+
})()
|
|
5071
|
+
);
|
|
5072
|
+
workspace.command("update <json>").description("Update workspace properties (pass JSON; prefer `rename`/`describe`/`public`)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
|
|
4795
5073
|
(json, opts) => runAction(async () => {
|
|
4796
5074
|
const body = parseJsonArg(json, `arbi workspace update '{"name": "New Name"}'`);
|
|
4797
5075
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
4798
5076
|
const data = await sdk.workspaces.updateWorkspace(arbi, body);
|
|
4799
|
-
if (opts.json) {
|
|
4800
|
-
console.log(JSON.stringify({ id: data.external_id, name: data.name }, null, 2));
|
|
4801
|
-
return;
|
|
4802
|
-
}
|
|
4803
5077
|
success(`Updated: ${data.name} (${ref(data.external_id)})`);
|
|
4804
5078
|
})()
|
|
4805
5079
|
);
|
|
@@ -4862,13 +5136,72 @@ function registerWorkspacesCommand(program2) {
|
|
|
4862
5136
|
for (const u of data) success(`Added: ${u.user.email} as ${u.role}`);
|
|
4863
5137
|
})()
|
|
4864
5138
|
);
|
|
4865
|
-
workspace.command("remove-user <
|
|
4866
|
-
(
|
|
5139
|
+
workspace.command("remove-user <users...>").description("Remove users from the active workspace (accepts usr-ids or emails)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--dry-run", "Preview which users would be removed (no SDK call)").action(
|
|
5140
|
+
(users, opts) => runAction(async () => {
|
|
4867
5141
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
5142
|
+
const needsLookup = users.some((u) => u.includes("@"));
|
|
5143
|
+
let userIds = users;
|
|
5144
|
+
if (needsLookup) {
|
|
5145
|
+
const members = await sdk.workspaces.listWorkspaceUsers(arbi);
|
|
5146
|
+
userIds = users.map((u) => {
|
|
5147
|
+
if (!u.includes("@")) return u;
|
|
5148
|
+
const hit = members.find((m) => m.email === u);
|
|
5149
|
+
if (!hit) {
|
|
5150
|
+
error(`Workspace member ${u} not found. Try: arbi workspace users`);
|
|
5151
|
+
process.exit(3);
|
|
5152
|
+
}
|
|
5153
|
+
return hit.external_id;
|
|
5154
|
+
});
|
|
5155
|
+
}
|
|
5156
|
+
if (opts.dryRun) {
|
|
5157
|
+
dryRun(`remove ${userIds.length} user(s) from this workspace`, userIds);
|
|
5158
|
+
return;
|
|
5159
|
+
}
|
|
4868
5160
|
await sdk.workspaces.removeWorkspaceUsers(arbi, userIds);
|
|
4869
5161
|
success(`Removed ${userIds.length} user(s).`);
|
|
4870
5162
|
})()
|
|
4871
5163
|
);
|
|
5164
|
+
workspace.command("leave [id-or-name]").description("Leave a workspace (defaults to the active one)").option("-y, --yes", "Skip confirmation (required in non-TTY shells)").option("--dry-run", "Preview which workspace would be left (no SDK call)").action(
|
|
5165
|
+
(selector, opts) => runAction(async () => {
|
|
5166
|
+
const { arbi: userArbi } = await resolveAuth();
|
|
5167
|
+
const wsList = await sdk.workspaces.listWorkspaces(userArbi);
|
|
5168
|
+
const target = selector ? resolveWorkspaceSelector(wsList, selector) : (() => {
|
|
5169
|
+
const activeId = getConfig()?.selectedWorkspaceId;
|
|
5170
|
+
const hit = wsList.find((w) => w.external_id === activeId);
|
|
5171
|
+
return hit ? { ok: true, id: hit.external_id, ws: hit } : { ok: false, reason: "not-found" };
|
|
5172
|
+
})();
|
|
5173
|
+
if (!target.ok) {
|
|
5174
|
+
printResolveError(wsList, selector ?? "(active workspace)", target);
|
|
5175
|
+
process.exit(3);
|
|
5176
|
+
}
|
|
5177
|
+
const selfExtId = userArbi.session.getState().userExtId;
|
|
5178
|
+
if (!selfExtId) {
|
|
5179
|
+
error("No user ID in session \u2014 cannot resolve self for leave.");
|
|
5180
|
+
process.exit(1);
|
|
5181
|
+
}
|
|
5182
|
+
if (opts.dryRun) {
|
|
5183
|
+
dryRun("leave workspace", `${target.id} ("${target.ws.name}")`);
|
|
5184
|
+
return;
|
|
5185
|
+
}
|
|
5186
|
+
if (!opts?.yes) {
|
|
5187
|
+
requireInteractive("Pass -y/--yes to confirm in non-TTY shells.");
|
|
5188
|
+
const confirmed = await promptConfirm(
|
|
5189
|
+
`Leave workspace "${target.ws.name}" (${target.id})?`,
|
|
5190
|
+
false
|
|
5191
|
+
);
|
|
5192
|
+
if (!confirmed) {
|
|
5193
|
+
console.log("Cancelled.");
|
|
5194
|
+
return;
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
const { arbi } = await resolveWorkspace(target.id);
|
|
5198
|
+
await sdk.workspaces.removeWorkspaceUsers(arbi, [selfExtId]);
|
|
5199
|
+
if (getConfig()?.selectedWorkspaceId === target.id) {
|
|
5200
|
+
updateConfig({ selectedWorkspaceId: "" });
|
|
5201
|
+
}
|
|
5202
|
+
success(`Left workspace "${target.ws.name}" (${target.id}).`);
|
|
5203
|
+
})()
|
|
5204
|
+
);
|
|
4872
5205
|
workspace.command("set-role <role> <user-ids...>").description("Update user roles (owner, collaborator, guest)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
|
|
4873
5206
|
(role, userIds, opts) => runAction(async () => {
|
|
4874
5207
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
@@ -4880,15 +5213,29 @@ function registerWorkspacesCommand(program2) {
|
|
|
4880
5213
|
for (const u of data) success(`Updated: ${u.user.email} \u2192 ${u.role}`);
|
|
4881
5214
|
})()
|
|
4882
5215
|
);
|
|
4883
|
-
workspace.command("copy <target-workspace
|
|
4884
|
-
(
|
|
5216
|
+
workspace.command("copy <target-workspace> <doc-ids...>").description("Copy documents to another workspace (target accepts ID or unique name)").option("-w, --workspace <id>", "Source workspace ID (defaults to selected workspace)").action(
|
|
5217
|
+
(targetRef, docIds, opts) => runAction(async () => {
|
|
4885
5218
|
const { arbi: userArbi } = await resolveAuth();
|
|
4886
5219
|
const wsList = await sdk.workspaces.listWorkspaces(userArbi);
|
|
4887
|
-
|
|
5220
|
+
let targetWs = wsList.find((w) => w.external_id === targetRef);
|
|
5221
|
+
if (!targetWs) {
|
|
5222
|
+
const nameMatches = wsList.filter(
|
|
5223
|
+
(w) => w.name?.toLowerCase() === targetRef.toLowerCase()
|
|
5224
|
+
);
|
|
5225
|
+
if (nameMatches.length === 1) {
|
|
5226
|
+
targetWs = nameMatches[0];
|
|
5227
|
+
} else if (nameMatches.length > 1) {
|
|
5228
|
+
error(
|
|
5229
|
+
`Ambiguous target workspace "${targetRef}" \u2014 matches ${nameMatches.map((w) => w.external_id).join(", ")}. Use the ID.`
|
|
5230
|
+
);
|
|
5231
|
+
process.exit(3);
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
4888
5234
|
if (!targetWs || !targetWs.wrapped_key) {
|
|
4889
|
-
error(`Target workspace ${
|
|
4890
|
-
process.exit(
|
|
5235
|
+
error(`Target workspace ${targetRef} not found or has no encryption key`);
|
|
5236
|
+
process.exit(3);
|
|
4891
5237
|
}
|
|
5238
|
+
const targetId = targetWs.external_id;
|
|
4892
5239
|
const { arbi, loginResult } = await resolveWorkspace(opts.workspace);
|
|
4893
5240
|
const signingPrivateKeyBase64 = arbi.crypto.bytesToBase64(loginResult.signingPrivateKey);
|
|
4894
5241
|
const targetKey = await sdk.generateEncryptedWorkspaceKey(
|
|
@@ -4913,6 +5260,7 @@ function registerWorkspacesCommand(program2) {
|
|
|
4913
5260
|
})()
|
|
4914
5261
|
);
|
|
4915
5262
|
}
|
|
5263
|
+
init_prompts();
|
|
4916
5264
|
function statusSymbol(status2) {
|
|
4917
5265
|
switch (status2) {
|
|
4918
5266
|
case "completed":
|
|
@@ -5235,8 +5583,8 @@ function registerDocsCommand(program2) {
|
|
|
5235
5583
|
}
|
|
5236
5584
|
})()
|
|
5237
5585
|
);
|
|
5238
|
-
doc.command("delete [ids...]").description("Delete documents (interactive picker if no IDs given)").action(
|
|
5239
|
-
(ids) => runAction(async () => {
|
|
5586
|
+
doc.command("delete [ids...]").description("Delete documents (interactive picker if no IDs given)").option("--dry-run", "Preview which docs would be deleted (no SDK call)").action(
|
|
5587
|
+
(ids, opts) => runAction(async () => {
|
|
5240
5588
|
const { arbi } = await resolveWorkspace();
|
|
5241
5589
|
let docIds = ids && ids.length > 0 ? ids : void 0;
|
|
5242
5590
|
if (!docIds) {
|
|
@@ -5245,26 +5593,94 @@ function registerDocsCommand(program2) {
|
|
|
5245
5593
|
if (selected.length === 0) return;
|
|
5246
5594
|
docIds = selected;
|
|
5247
5595
|
}
|
|
5596
|
+
if (opts?.dryRun) {
|
|
5597
|
+
dryRun(`delete ${docIds.length} document(s)`, docIds);
|
|
5598
|
+
return;
|
|
5599
|
+
}
|
|
5248
5600
|
await sdk.documents.deleteDocuments(arbi, docIds);
|
|
5249
5601
|
success(`Deleted ${docIds.length} document(s).`);
|
|
5250
5602
|
})()
|
|
5251
5603
|
);
|
|
5252
|
-
doc.command("
|
|
5253
|
-
(
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5604
|
+
doc.command("share <ids...>").description("Mark documents as shared (visible to all workspace members)").action(
|
|
5605
|
+
(ids) => runAction(async () => {
|
|
5606
|
+
const { arbi } = await resolveWorkspace();
|
|
5607
|
+
await sdk.documents.updateDocuments(
|
|
5608
|
+
arbi,
|
|
5609
|
+
ids.map((external_id) => ({ external_id, shared: true }))
|
|
5610
|
+
);
|
|
5611
|
+
success(`Shared ${ids.length} document(s).`);
|
|
5612
|
+
})()
|
|
5613
|
+
);
|
|
5614
|
+
doc.command("unshare <ids...>").description("Mark documents as private (owner-only)").action(
|
|
5615
|
+
(ids) => runAction(async () => {
|
|
5616
|
+
const { arbi } = await resolveWorkspace();
|
|
5617
|
+
await sdk.documents.updateDocuments(
|
|
5618
|
+
arbi,
|
|
5619
|
+
ids.map((external_id) => ({ external_id, shared: false }))
|
|
5620
|
+
);
|
|
5621
|
+
success(`Unshared ${ids.length} document(s).`);
|
|
5622
|
+
})()
|
|
5623
|
+
);
|
|
5624
|
+
doc.command("similar [doc-id]").description("Find near-duplicate documents (cached similarity pairs)").option(
|
|
5625
|
+
"--threshold <n>",
|
|
5626
|
+
"Minimum cosine similarity score, 0.0\u20131.0 (default 0.0 = all stored pairs)",
|
|
5627
|
+
(v) => parseFloat(v),
|
|
5628
|
+
0
|
|
5629
|
+
).option("--json", "Output as JSON").action(
|
|
5630
|
+
(docId, opts) => runAction(async () => {
|
|
5631
|
+
const { arbi } = await resolveWorkspace();
|
|
5632
|
+
const res = await arbi.fetch.GET("/v1/document/similar", {
|
|
5633
|
+
params: {
|
|
5634
|
+
query: {
|
|
5635
|
+
...docId ? { doc_ext_id: docId } : {},
|
|
5636
|
+
threshold: opts.threshold
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
});
|
|
5640
|
+
if (res.error) throw new Error(`similar failed: ${JSON.stringify(res.error)}`);
|
|
5641
|
+
const envelope = res.data;
|
|
5642
|
+
const pairs = envelope.pairs ?? [];
|
|
5643
|
+
if (opts.json) {
|
|
5644
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
5645
|
+
return;
|
|
5646
|
+
}
|
|
5647
|
+
if (pairs.length === 0) {
|
|
5648
|
+
process.stderr.write(
|
|
5649
|
+
docId ? `No similar documents found above threshold ${opts.threshold}.
|
|
5650
|
+
` : "No stored similarity pairs.\n"
|
|
5651
|
+
);
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
printTable(
|
|
5655
|
+
[
|
|
5656
|
+
{ header: "DOC A", width: 18, value: (r) => r.doc_a_ext_id ?? "" },
|
|
5657
|
+
{ header: "DOC B", width: 18, value: (r) => r.doc_b_ext_id ?? "" },
|
|
5658
|
+
{
|
|
5659
|
+
header: "SCORE",
|
|
5660
|
+
width: 8,
|
|
5661
|
+
value: (r) => (r.score ?? 0).toFixed(3)
|
|
5662
|
+
}
|
|
5663
|
+
],
|
|
5664
|
+
pairs
|
|
5665
|
+
);
|
|
5666
|
+
})()
|
|
5667
|
+
);
|
|
5668
|
+
doc.command("update [json]").description("Update documents (interactive form if no JSON given)").action(
|
|
5669
|
+
(json) => runAction(async () => {
|
|
5670
|
+
if (json) {
|
|
5671
|
+
const parsed = parseJsonArg(json, `arbi doc update '[{"external_id": "doc-123", "shared": true}]'`);
|
|
5672
|
+
const docs = Array.isArray(parsed) ? parsed : parsed.documents;
|
|
5673
|
+
const { arbi } = await resolveWorkspace();
|
|
5674
|
+
const data = await sdk.documents.updateDocuments(
|
|
5675
|
+
arbi,
|
|
5676
|
+
docs
|
|
5677
|
+
);
|
|
5678
|
+
success(`Updated ${data.length} document(s).`);
|
|
5679
|
+
} else {
|
|
5680
|
+
const { arbi } = await resolveWorkspace();
|
|
5681
|
+
const choices = await fetchDocChoices(arbi);
|
|
5682
|
+
const docId = await promptSearch("Select document to update", choices);
|
|
5683
|
+
const field = await promptSelect("Field to update", [
|
|
5268
5684
|
{ name: "Title", value: "title" },
|
|
5269
5685
|
{ name: "Shared", value: "shared" },
|
|
5270
5686
|
{ name: "Author", value: "author" },
|
|
@@ -5344,9 +5760,9 @@ function registerDocsCommand(program2) {
|
|
|
5344
5760
|
"5"
|
|
5345
5761
|
).option("-v, --verbose", "Print a log line after each batch in addition to periodic stats.").option("-q, --quiet", "Suppress periodic progress lines \u2014 only print the final summary.").option("-W, --watch", "Watch document processing progress after submission").option("--no-watch", "Skip watching document processing").action(
|
|
5346
5762
|
(ids, opts) => runAction(async () => {
|
|
5347
|
-
const
|
|
5763
|
+
const isInteractive2 = process.stdout.isTTY === true;
|
|
5348
5764
|
const watchPref = getConfig()?.watch !== false;
|
|
5349
|
-
const willWatch = opts.watch === false ? false : opts.watch === true || watchPref &&
|
|
5765
|
+
const willWatch = opts.watch === false ? false : opts.watch === true || watchPref && isInteractive2;
|
|
5350
5766
|
const { arbi, config, accessToken } = await resolveWorkspace(void 0, {
|
|
5351
5767
|
skipNotifications: true
|
|
5352
5768
|
});
|
|
@@ -5440,7 +5856,7 @@ function registerDocsCommand(program2) {
|
|
|
5440
5856
|
if (!opts.quiet && statusIntervalMs > 0) {
|
|
5441
5857
|
statusTimer = setInterval(() => printStatus(" "), statusIntervalMs);
|
|
5442
5858
|
}
|
|
5443
|
-
const sleep = (ms) => new Promise((
|
|
5859
|
+
const sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
5444
5860
|
const backoff = async (attempt) => {
|
|
5445
5861
|
const delayMs = Math.min(1e3 * Math.pow(2, attempt), 3e4);
|
|
5446
5862
|
await sleep(delayMs);
|
|
@@ -5718,9 +6134,9 @@ function classifyDirectInputs(inputPaths) {
|
|
|
5718
6134
|
return out;
|
|
5719
6135
|
}
|
|
5720
6136
|
async function runDirectUploadMode(inputPaths, opts) {
|
|
5721
|
-
const
|
|
6137
|
+
const isInteractive2 = process.stdout.isTTY === true;
|
|
5722
6138
|
const watchPref = getConfig()?.watch !== false;
|
|
5723
|
-
const willWatch = opts.watch === false ? false : opts.watch === true || watchPref &&
|
|
6139
|
+
const willWatch = opts.watch === false ? false : opts.watch === true || watchPref && isInteractive2;
|
|
5724
6140
|
const inputs = classifyDirectInputs(inputPaths);
|
|
5725
6141
|
if (inputs.length === 0) {
|
|
5726
6142
|
warn("No supported files found to upload.");
|
|
@@ -5949,9 +6365,9 @@ function registerUploadCommand(program2) {
|
|
|
5949
6365
|
process.exit(1);
|
|
5950
6366
|
}
|
|
5951
6367
|
}
|
|
5952
|
-
const
|
|
6368
|
+
const isInteractive2 = process.stdout.isTTY === true;
|
|
5953
6369
|
const watchPref = getConfig()?.watch !== false;
|
|
5954
|
-
const willWatch = opts.watch === false ? false : opts.watch === true || watchPref &&
|
|
6370
|
+
const willWatch = opts.watch === false ? false : opts.watch === true || watchPref && isInteractive2;
|
|
5955
6371
|
const { config, accessToken } = await resolveWorkspace(opts.workspace, {
|
|
5956
6372
|
skipNotifications: willWatch
|
|
5957
6373
|
});
|
|
@@ -6127,7 +6543,7 @@ Watching ${pending.size} document(s)...`);
|
|
|
6127
6543
|
pending.delete(msg.doc_ext_id);
|
|
6128
6544
|
} else {
|
|
6129
6545
|
console.log(` ${docName}: ${status(msg.status)} (${msg.progress}%)`);
|
|
6130
|
-
if (msg.status
|
|
6546
|
+
if (sdk.DOC_TERMINAL_STATUSES.has(msg.status)) {
|
|
6131
6547
|
pending.delete(msg.doc_ext_id);
|
|
6132
6548
|
}
|
|
6133
6549
|
}
|
|
@@ -6415,7 +6831,7 @@ async function runManifestMode(inputPaths, opts) {
|
|
|
6415
6831
|
pathsToUpload = pathsToUpload.slice(0, n);
|
|
6416
6832
|
}
|
|
6417
6833
|
}
|
|
6418
|
-
const
|
|
6834
|
+
const isInteractive2 = process.stdout.isTTY === true;
|
|
6419
6835
|
const humanOutput = !opts.json;
|
|
6420
6836
|
if (humanOutput) {
|
|
6421
6837
|
console.log(
|
|
@@ -6441,7 +6857,7 @@ async function runManifestMode(inputPaths, opts) {
|
|
|
6441
6857
|
return;
|
|
6442
6858
|
}
|
|
6443
6859
|
const watchPref = getConfig()?.watch !== false;
|
|
6444
|
-
const willWatch = opts.watch === false ? false : opts.watch === true || watchPref &&
|
|
6860
|
+
const willWatch = opts.watch === false ? false : opts.watch === true || watchPref && isInteractive2;
|
|
6445
6861
|
const { config, accessToken, workspaceId } = await resolveWorkspace(opts.workspace, {
|
|
6446
6862
|
skipNotifications: willWatch
|
|
6447
6863
|
});
|
|
@@ -6489,13 +6905,13 @@ async function runManifestMode(inputPaths, opts) {
|
|
|
6489
6905
|
let errorCount = 0;
|
|
6490
6906
|
let skippedCount = 0;
|
|
6491
6907
|
const renderProgress = () => {
|
|
6492
|
-
if (!humanOutput || !
|
|
6908
|
+
if (!humanOutput || !isInteractive2) return;
|
|
6493
6909
|
const pct = total > 0 ? Math.floor(done / total * 100) : 100;
|
|
6494
6910
|
const line = ` ${done}/${total} (${pct}%) ok=${uploadedCount}` + (duplicateCount > 0 ? ` dup=${duplicateCount}` : "") + (rejectedCount > 0 ? ` rej=${rejectedCount}` : "") + (skippedCount > 0 ? ` skip=${skippedCount}` : "") + (errorCount > 0 ? ` err=${errorCount}` : "");
|
|
6495
6911
|
process.stderr.write("\r" + line.padEnd(80, " "));
|
|
6496
6912
|
};
|
|
6497
6913
|
const clearProgress = () => {
|
|
6498
|
-
if (humanOutput &&
|
|
6914
|
+
if (humanOutput && isInteractive2) process.stderr.write("\r" + " ".repeat(80) + "\r");
|
|
6499
6915
|
};
|
|
6500
6916
|
const printInlineOutcome = (o) => {
|
|
6501
6917
|
if (!humanOutput) return;
|
|
@@ -6628,7 +7044,7 @@ Watching ${pending.size} document(s)...`);
|
|
|
6628
7044
|
pending.delete(msg.doc_ext_id);
|
|
6629
7045
|
} else {
|
|
6630
7046
|
console.log(` ${docName}: ${status(msg.status)} (${msg.progress}%)`);
|
|
6631
|
-
if (msg.status
|
|
7047
|
+
if (sdk.DOC_TERMINAL_STATUSES.has(msg.status)) {
|
|
6632
7048
|
pending.delete(msg.doc_ext_id);
|
|
6633
7049
|
}
|
|
6634
7050
|
}
|
|
@@ -6658,6 +7074,7 @@ Connection closed. ${pending.size} document(s) still processing.`);
|
|
|
6658
7074
|
await done2;
|
|
6659
7075
|
}
|
|
6660
7076
|
}
|
|
7077
|
+
init_prompts();
|
|
6661
7078
|
function registerDownloadCommand(program2) {
|
|
6662
7079
|
program2.command("download [doc-id]").description("Download a document (interactive picker if no ID given)").option(
|
|
6663
7080
|
"-o, --output <path>",
|
|
@@ -6716,8 +7133,17 @@ function registerDownloadCommand(program2) {
|
|
|
6716
7133
|
);
|
|
6717
7134
|
}
|
|
6718
7135
|
function registerAskCommand(program2) {
|
|
6719
|
-
program2.command("ask <question...>").description("Ask the RAG assistant a question (no quotes needed)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-c, --continue <msg-id>", "Continue from a specific message ID").option("--config <id>", "Config ext_id to use (e.g. cfg-xxx)").option("-n, --new", "Start a new conversation (ignore previous context)").option("-b, --background", "Submit as background task
|
|
7136
|
+
program2.command("ask <question...>").description("Ask the RAG assistant a question (no quotes needed)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-c, --continue <msg-id>", "Continue from a specific message ID").option("--config <id>", "Config ext_id to use (e.g. cfg-xxx)").option("-n, --new", "Start a new conversation (ignore previous context)").option("-b, --background", "Submit as background task \u2014 fetch result with `arbi task result`").option("-q, --quiet", "Suppress agent steps and tool calls").option("--json", "Output in JSON format (background mode only)").option(
|
|
7137
|
+
"--raw-citations",
|
|
7138
|
+
"Keep raw `[text](#cite-N)` markdown links instead of the cleaner `text[N]` form"
|
|
7139
|
+
).action(
|
|
6720
7140
|
(words, opts) => runAction(async () => {
|
|
7141
|
+
if (opts.json && !opts.background) {
|
|
7142
|
+
console.error(
|
|
7143
|
+
"Error: --json only works with -b/--background. Streaming mode emits prose, not JSON.\nRun: arbi ask -b --json <question> (then `arbi task result`)"
|
|
7144
|
+
);
|
|
7145
|
+
process.exit(1);
|
|
7146
|
+
}
|
|
6721
7147
|
const question = words.join(" ");
|
|
6722
7148
|
const { arbi, accessToken, workspaceId, config } = await resolveWorkspace(opts.workspace);
|
|
6723
7149
|
let previousResponseId = null;
|
|
@@ -6792,13 +7218,23 @@ function registerAskCommand(program2) {
|
|
|
6792
7218
|
const verbose = opts.quiet === true ? false : getConfig()?.verbose !== false;
|
|
6793
7219
|
let elapsedTime = null;
|
|
6794
7220
|
let firstToken = true;
|
|
7221
|
+
let buf = "";
|
|
7222
|
+
const transform = opts.rawCitations ? (s) => s : sdk.stripCitationMarkdown;
|
|
7223
|
+
const flush = (final) => {
|
|
7224
|
+
const cut = final ? buf.length : Math.max(0, buf.length - 32);
|
|
7225
|
+
if (cut === 0) return;
|
|
7226
|
+
const out = transform(buf.slice(0, cut));
|
|
7227
|
+
buf = buf.slice(cut);
|
|
7228
|
+
if (out) process.stdout.write(out);
|
|
7229
|
+
};
|
|
6795
7230
|
const result = await sdk.streamSSE(res, {
|
|
6796
7231
|
onToken: (content) => {
|
|
6797
7232
|
if (firstToken) {
|
|
6798
7233
|
process.stderr.write(chalk2__default.default.dim("[ARBI] "));
|
|
6799
7234
|
firstToken = false;
|
|
6800
7235
|
}
|
|
6801
|
-
|
|
7236
|
+
buf += content;
|
|
7237
|
+
flush(false);
|
|
6802
7238
|
},
|
|
6803
7239
|
onAgentStep: (data) => {
|
|
6804
7240
|
if (verbose) {
|
|
@@ -6815,6 +7251,7 @@ function registerAskCommand(program2) {
|
|
|
6815
7251
|
onError: (message) => console.error(chalk2__default.default.red(`
|
|
6816
7252
|
Error: ${message}`))
|
|
6817
7253
|
});
|
|
7254
|
+
flush(true);
|
|
6818
7255
|
process.stdout.write("\n");
|
|
6819
7256
|
if (result.metadata) {
|
|
6820
7257
|
saveLastMetadata(result.metadata);
|
|
@@ -6833,9 +7270,10 @@ Error: ${message}`))
|
|
|
6833
7270
|
chalk2__default.default.dim(`[${refs} citation${refs === 1 ? "" : "s"} \u2014 arbi cite to browse]`)
|
|
6834
7271
|
);
|
|
6835
7272
|
}
|
|
6836
|
-
|
|
7273
|
+
const assistantId = result.assistantMessageExtId ?? result.metadata?.external_id;
|
|
7274
|
+
if (assistantId) {
|
|
6837
7275
|
const updates = {
|
|
6838
|
-
lastMessageExtId:
|
|
7276
|
+
lastMessageExtId: assistantId,
|
|
6839
7277
|
workspaceId
|
|
6840
7278
|
};
|
|
6841
7279
|
const conversationExtId = result.userMessage?.conversation_ext_id ?? result.metadata?.conversation_ext_id;
|
|
@@ -6843,96 +7281,202 @@ Error: ${message}`))
|
|
|
6843
7281
|
updates.conversationExtId = conversationExtId;
|
|
6844
7282
|
}
|
|
6845
7283
|
updateChatSession(updates);
|
|
7284
|
+
} else if (opts.quiet !== true) {
|
|
7285
|
+
console.error(
|
|
7286
|
+
chalk2__default.default.yellow(
|
|
7287
|
+
"[warn] No assistant message ID received \u2014 session not saved. The next `arbi ask` will start a new conversation."
|
|
7288
|
+
)
|
|
7289
|
+
);
|
|
6846
7290
|
}
|
|
6847
7291
|
})()
|
|
6848
7292
|
);
|
|
6849
7293
|
}
|
|
6850
|
-
|
|
7294
|
+
function hashUtf8(s) {
|
|
7295
|
+
return crypto$1.createHash("sha256").update(s, "utf8").digest("hex");
|
|
7296
|
+
}
|
|
6851
7297
|
function registerCiteCommand(program2) {
|
|
6852
|
-
program2.command("cite [number]").description("Browse citations from the last response").option("-a, --all", "Show all citations
|
|
6853
|
-
(
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
if (count === 0) {
|
|
6862
|
-
console.log(chalk2__default.default.dim("The last response contained no citations."));
|
|
6863
|
-
return;
|
|
6864
|
-
}
|
|
6865
|
-
const resolved = sdk.resolveCitations(metadata);
|
|
6866
|
-
if (opts.all) {
|
|
6867
|
-
for (const r of resolved) {
|
|
6868
|
-
printCitationDetail(r.citationNum, resolved);
|
|
6869
|
-
console.log();
|
|
7298
|
+
program2.command("cite [number]").description("Browse citations from the last `arbi ask` response in this shell").allowExcessArguments(true).option("-a, --all", "Show all citations as markdown blocks").option("--json", "Output citations as JSON (full structured payload)").action(
|
|
7299
|
+
(numberOrVerb, opts) => runAction(async () => {
|
|
7300
|
+
if (numberOrVerb === "verify") {
|
|
7301
|
+
const argv = process.argv;
|
|
7302
|
+
const i = argv.indexOf("verify");
|
|
7303
|
+
const target = i !== -1 ? argv[i + 1] : void 0;
|
|
7304
|
+
if (!target) {
|
|
7305
|
+
console.error("Usage: arbi cite verify <number>");
|
|
7306
|
+
process.exit(1);
|
|
6870
7307
|
}
|
|
7308
|
+
await verifyOne(target, opts);
|
|
6871
7309
|
return;
|
|
6872
7310
|
}
|
|
6873
|
-
|
|
6874
|
-
printCitationDetail(number, resolved);
|
|
6875
|
-
return;
|
|
6876
|
-
}
|
|
6877
|
-
const summaries = sdk.summarizeCitations(resolved);
|
|
6878
|
-
console.log(chalk2__default.default.bold(`Citations (${summaries.length}):
|
|
6879
|
-
`));
|
|
6880
|
-
for (const s of summaries) {
|
|
6881
|
-
const page = s.pageNumber != null ? `, p${s.pageNumber}` : "";
|
|
6882
|
-
const chunks = s.chunkCount > 1 ? ` (${s.chunkCount} passages)` : "";
|
|
6883
|
-
console.log(
|
|
6884
|
-
` ${chalk2__default.default.cyan(`[${s.citationNum}]`)} ${chalk2__default.default.bold(s.docTitle)}${page}${chunks}`
|
|
6885
|
-
);
|
|
6886
|
-
const truncated = s.statement.length > 120 ? s.statement.slice(0, 120).replace(/\n/g, " ").trim() + "..." : s.statement.replace(/\n/g, " ").trim();
|
|
6887
|
-
console.log(` ${chalk2__default.default.dim(truncated)}`);
|
|
6888
|
-
}
|
|
6889
|
-
console.log(chalk2__default.default.dim(`
|
|
6890
|
-
Use \`arbi cite <N>\` to view full passage.`));
|
|
7311
|
+
await listOrShow(numberOrVerb, opts);
|
|
6891
7312
|
})()
|
|
6892
7313
|
);
|
|
6893
7314
|
}
|
|
6894
|
-
function
|
|
6895
|
-
const
|
|
6896
|
-
if (!citation) {
|
|
6897
|
-
console.error(chalk2__default.default.red(`Citation [${num}] not found.`));
|
|
6898
|
-
return;
|
|
6899
|
-
}
|
|
6900
|
-
const firstChunk = citation.chunks[0];
|
|
7315
|
+
function renderCitation(r) {
|
|
7316
|
+
const firstChunk = r.chunks[0];
|
|
6901
7317
|
const docTitle = firstChunk?.metadata?.doc_title ?? "Unknown document";
|
|
6902
7318
|
const page = firstChunk?.metadata?.page_number;
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
7319
|
+
const docId = firstChunk?.metadata?.doc_ext_id ?? null;
|
|
7320
|
+
const chunkId = firstChunk?.metadata?.chunk_ext_id ?? null;
|
|
7321
|
+
const passage = r.chunks.map((c) => c.content).join("\n\n");
|
|
7322
|
+
const sha = hashUtf8(passage);
|
|
7323
|
+
const lines = [];
|
|
7324
|
+
lines.push(chalk2__default.default.bold(`## Citation ${r.citationNum}`));
|
|
7325
|
+
lines.push("");
|
|
7326
|
+
lines.push(`${chalk2__default.default.bold("**Document:**")} ${docTitle}`);
|
|
7327
|
+
if (page != null) lines.push(`${chalk2__default.default.bold("**Page:**")} ${page}`);
|
|
7328
|
+
if (docId) lines.push(`${chalk2__default.default.bold("**Doc ID:**")} ${docId}`);
|
|
7329
|
+
if (chunkId) lines.push(`${chalk2__default.default.bold("**Chunk ID:**")} ${chunkId}`);
|
|
7330
|
+
lines.push(`${chalk2__default.default.bold("**SHA256:**")} ${sha}`);
|
|
7331
|
+
lines.push("");
|
|
7332
|
+
lines.push(chalk2__default.default.bold("### Claim"));
|
|
7333
|
+
lines.push(r.citationData.statement ?? "");
|
|
7334
|
+
lines.push("");
|
|
7335
|
+
lines.push(chalk2__default.default.bold("### Passage"));
|
|
7336
|
+
lines.push("```");
|
|
7337
|
+
lines.push(passage);
|
|
7338
|
+
lines.push("```");
|
|
7339
|
+
return lines.join("\n");
|
|
7340
|
+
}
|
|
7341
|
+
async function listOrShow(number, opts) {
|
|
7342
|
+
const raw = loadLastMetadata();
|
|
7343
|
+
if (!raw) {
|
|
7344
|
+
if (opts.json) {
|
|
7345
|
+
printJson({ count: 0, citations: [] });
|
|
7346
|
+
return;
|
|
6917
7347
|
}
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
7348
|
+
process.stderr.write("No citation data available. Run `arbi ask` first.\n");
|
|
7349
|
+
return;
|
|
7350
|
+
}
|
|
7351
|
+
const metadata = raw;
|
|
7352
|
+
if (sdk.countCitations(metadata) === 0) {
|
|
7353
|
+
if (opts.json) {
|
|
7354
|
+
printJson({ count: 0, citations: [] });
|
|
7355
|
+
return;
|
|
7356
|
+
}
|
|
7357
|
+
process.stderr.write("The last response contained no citations.\n");
|
|
7358
|
+
return;
|
|
7359
|
+
}
|
|
7360
|
+
const resolved = sdk.resolveCitations(metadata);
|
|
7361
|
+
if (opts.json) {
|
|
7362
|
+
const projection = resolved.map((r) => {
|
|
7363
|
+
const passage = r.chunks.map((c) => c.content).join("\n\n");
|
|
7364
|
+
return {
|
|
7365
|
+
num: r.citationNum,
|
|
7366
|
+
statement: r.citationData.statement ?? "",
|
|
7367
|
+
doc_title: r.chunks[0]?.metadata?.doc_title ?? null,
|
|
7368
|
+
page_number: r.chunks[0]?.metadata?.page_number ?? null,
|
|
7369
|
+
chunk_ext_ids: r.chunks.map((c) => c.metadata?.chunk_ext_id ?? null),
|
|
7370
|
+
doc_ext_ids: [...new Set(r.chunks.map((c) => c.metadata?.doc_ext_id).filter(Boolean))],
|
|
7371
|
+
passages: r.chunks.map((c) => c.content),
|
|
7372
|
+
passage_hashes: r.chunks.map((c) => hashUtf8(c.content)),
|
|
7373
|
+
combined_passage_hash: hashUtf8(passage)
|
|
7374
|
+
};
|
|
7375
|
+
});
|
|
7376
|
+
const filtered = number ? projection.filter((p) => p.num === number) : projection;
|
|
7377
|
+
printJson({ count: filtered.length, citations: filtered });
|
|
7378
|
+
return;
|
|
7379
|
+
}
|
|
7380
|
+
if (number) {
|
|
7381
|
+
const hit = resolved.find((r) => r.citationNum === number);
|
|
7382
|
+
if (!hit) {
|
|
7383
|
+
console.error(chalk2__default.default.red(`Citation [${number}] not found.`));
|
|
7384
|
+
process.exit(3);
|
|
7385
|
+
}
|
|
7386
|
+
console.log(renderCitation(hit));
|
|
7387
|
+
return;
|
|
7388
|
+
}
|
|
7389
|
+
if (opts.all) {
|
|
7390
|
+
for (let i = 0; i < resolved.length; i++) {
|
|
7391
|
+
if (i > 0) console.log("\n---\n");
|
|
7392
|
+
console.log(renderCitation(resolved[i]));
|
|
7393
|
+
}
|
|
7394
|
+
return;
|
|
7395
|
+
}
|
|
7396
|
+
console.log(chalk2__default.default.bold(`# Citations (${resolved.length})`));
|
|
7397
|
+
console.log("");
|
|
7398
|
+
for (const r of resolved) {
|
|
7399
|
+
const docTitle = r.chunks[0]?.metadata?.doc_title ?? "Unknown document";
|
|
7400
|
+
const page = r.chunks[0]?.metadata?.page_number;
|
|
7401
|
+
const pageSuffix = page != null ? `, p${page}` : "";
|
|
7402
|
+
const docId = r.chunks[0]?.metadata?.doc_ext_id;
|
|
7403
|
+
const statement = (r.citationData.statement ?? "").replace(/\s+/g, " ").trim();
|
|
7404
|
+
const trimmed = statement.length > 140 ? statement.slice(0, 140) + "\u2026" : statement;
|
|
7405
|
+
console.log(
|
|
7406
|
+
`- ${chalk2__default.default.bold(`[${r.citationNum}]`)} ${docTitle}${pageSuffix}${docId ? ` (${docId})` : ""}`
|
|
7407
|
+
);
|
|
7408
|
+
console.log(` ${trimmed}`);
|
|
7409
|
+
}
|
|
7410
|
+
console.log("");
|
|
7411
|
+
console.log(
|
|
7412
|
+
chalk2__default.default.dim("`arbi cite <N>` for the full markdown block; `--json` for the structured payload.")
|
|
7413
|
+
);
|
|
7414
|
+
}
|
|
7415
|
+
async function verifyOne(number, opts) {
|
|
7416
|
+
const raw = loadLastMetadata();
|
|
7417
|
+
if (!raw) {
|
|
7418
|
+
if (opts.json) {
|
|
7419
|
+
printJson({ verified: false, reason: "no-last-metadata" });
|
|
7420
|
+
return;
|
|
7421
|
+
}
|
|
7422
|
+
process.stderr.write("No citation data available. Run `arbi ask` first.\n");
|
|
7423
|
+
process.exit(1);
|
|
7424
|
+
}
|
|
7425
|
+
const metadata = raw;
|
|
7426
|
+
const resolved = sdk.resolveCitations(metadata);
|
|
7427
|
+
const citation = resolved.find((r) => r.citationNum === number);
|
|
7428
|
+
if (!citation) {
|
|
7429
|
+
if (opts.json) {
|
|
7430
|
+
printJson({ verified: false, reason: "not-found", number });
|
|
7431
|
+
return;
|
|
6924
7432
|
}
|
|
6925
|
-
|
|
7433
|
+
console.error(chalk2__default.default.red(`Citation [${number}] not found in last response.`));
|
|
7434
|
+
process.exit(3);
|
|
7435
|
+
}
|
|
7436
|
+
const chunks = citation.chunks.map((c) => ({
|
|
7437
|
+
chunk_ext_id: c.metadata?.chunk_ext_id ?? null,
|
|
7438
|
+
doc_ext_id: c.metadata?.doc_ext_id ?? null,
|
|
7439
|
+
doc_title: c.metadata?.doc_title ?? null,
|
|
7440
|
+
page_number: c.metadata?.page_number ?? null,
|
|
7441
|
+
passage_length: c.content.length,
|
|
7442
|
+
passage_sha256: hashUtf8(c.content)
|
|
7443
|
+
}));
|
|
7444
|
+
if (opts.json) {
|
|
7445
|
+
printJson({
|
|
7446
|
+
verified: chunks.length > 0,
|
|
7447
|
+
number,
|
|
7448
|
+
statement: citation.citationData.statement ?? "",
|
|
7449
|
+
chunks,
|
|
7450
|
+
followup_hint: "For independent re-verification, run `arbi doc get <doc_ext_id> --json` and confirm the passage hash matches the doc content."
|
|
7451
|
+
});
|
|
7452
|
+
return;
|
|
6926
7453
|
}
|
|
6927
|
-
|
|
6928
|
-
|
|
7454
|
+
console.log(chalk2__default.default.bold(`## Verification: Citation ${number}`));
|
|
7455
|
+
console.log("");
|
|
7456
|
+
console.log(`${chalk2__default.default.bold("**Status:**")} ${chunks.length > 0 ? "verified" : "no chunks"}`);
|
|
7457
|
+
console.log(`${chalk2__default.default.bold("**Claim:**")} ${citation.citationData.statement ?? ""}`);
|
|
7458
|
+
console.log("");
|
|
7459
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
7460
|
+
const v = chunks[i];
|
|
7461
|
+
console.log(chalk2__default.default.bold(`### Chunk ${i + 1}/${chunks.length}`));
|
|
7462
|
+
console.log(`${chalk2__default.default.bold("**Doc:**")} ${v.doc_title ?? "Unknown"}`);
|
|
7463
|
+
if (v.page_number != null) console.log(`${chalk2__default.default.bold("**Page:**")} ${v.page_number}`);
|
|
7464
|
+
if (v.doc_ext_id) console.log(`${chalk2__default.default.bold("**Doc ID:**")} ${v.doc_ext_id}`);
|
|
7465
|
+
if (v.chunk_ext_id) console.log(`${chalk2__default.default.bold("**Chunk ID:**")} ${v.chunk_ext_id}`);
|
|
7466
|
+
console.log(`${chalk2__default.default.bold("**Length:**")} ${v.passage_length} chars`);
|
|
7467
|
+
console.log(`${chalk2__default.default.bold("**SHA256:**")} ${v.passage_sha256}`);
|
|
7468
|
+
if (i < chunks.length - 1) console.log("");
|
|
6929
7469
|
}
|
|
7470
|
+
console.log("");
|
|
7471
|
+
console.log(
|
|
7472
|
+
chalk2__default.default.dim("Re-verify: `arbi doc get <Doc ID> --json` and compare the passage SHA256.")
|
|
7473
|
+
);
|
|
6930
7474
|
}
|
|
6931
7475
|
var MAX_INDIVIDUALLY_SELECTED_DOCS = 5e3;
|
|
6932
7476
|
function chunkScore(chunk) {
|
|
6933
7477
|
return chunk.metadata.rerank_score ?? chunk.metadata.score ?? 0;
|
|
6934
7478
|
}
|
|
6935
|
-
function
|
|
7479
|
+
function truncate2(text, max) {
|
|
6936
7480
|
const oneLine = text.replace(/\n/g, " ").trim();
|
|
6937
7481
|
return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
|
|
6938
7482
|
}
|
|
@@ -6987,11 +7531,22 @@ Narrow your selection, or omit -d to search the whole workspace.`
|
|
|
6987
7531
|
const filtered = allChunks.filter((c) => chunkScore(c) >= minScore).sort((a, b) => chunkScore(b) - chunkScore(a)).slice(0, limit);
|
|
6988
7532
|
const uniqueDocs = new Set(filtered.map((c) => c.metadata.doc_ext_id));
|
|
6989
7533
|
if (opts.json) {
|
|
6990
|
-
console.log(
|
|
7534
|
+
console.log(
|
|
7535
|
+
JSON.stringify(
|
|
7536
|
+
{
|
|
7537
|
+
results: filtered,
|
|
7538
|
+
count: filtered.length,
|
|
7539
|
+
unique_documents: uniqueDocs.size,
|
|
7540
|
+
raw: result
|
|
7541
|
+
},
|
|
7542
|
+
null,
|
|
7543
|
+
2
|
|
7544
|
+
)
|
|
7545
|
+
);
|
|
6991
7546
|
return;
|
|
6992
7547
|
}
|
|
6993
7548
|
if (filtered.length === 0) {
|
|
6994
|
-
|
|
7549
|
+
process.stderr.write("No results found.\n");
|
|
6995
7550
|
return;
|
|
6996
7551
|
}
|
|
6997
7552
|
console.log(
|
|
@@ -7005,7 +7560,7 @@ Found ${chalk2__default.default.bold(String(filtered.length))} result${filtered.
|
|
|
7005
7560
|
const score = fmtScore(chunkScore(chunk));
|
|
7006
7561
|
const doc = chunk.metadata.doc_title ?? chunk.metadata.doc_ext_id ?? "";
|
|
7007
7562
|
const page = chunk.metadata.page_number ? `p.${chunk.metadata.page_number}` : "";
|
|
7008
|
-
const preview =
|
|
7563
|
+
const preview = truncate2(chunk.content, 80);
|
|
7009
7564
|
console.log(
|
|
7010
7565
|
` ${chalk2__default.default.dim(String(i + 1).padStart(2, " "))}. ${chalk2__default.default.yellow(score)} ${chalk2__default.default.cyan(doc)} ${chalk2__default.default.dim(page)}`
|
|
7011
7566
|
);
|
|
@@ -7030,7 +7585,7 @@ Found ${chalk2__default.default.bold(String(filtered.length))} result${filtered.
|
|
|
7030
7585
|
for (const chunk of chunks) {
|
|
7031
7586
|
const score = fmtScore(chunkScore(chunk));
|
|
7032
7587
|
const page = chunk.metadata.page_number ? `p.${chunk.metadata.page_number}` : "";
|
|
7033
|
-
const preview =
|
|
7588
|
+
const preview = truncate2(chunk.content, 72);
|
|
7034
7589
|
console.log(
|
|
7035
7590
|
` ${chalk2__default.default.yellow(score)} ${chalk2__default.default.dim(page.padEnd(6))} ${chalk2__default.default.dim(preview)}`
|
|
7036
7591
|
);
|
|
@@ -7046,8 +7601,19 @@ function colorize2(level, text) {
|
|
|
7046
7601
|
if (level === "warning") return chalk2__default.default.yellow(text);
|
|
7047
7602
|
return text;
|
|
7048
7603
|
}
|
|
7604
|
+
var KNOWN_WATCH_TYPES = [
|
|
7605
|
+
"task_update",
|
|
7606
|
+
"batch_complete",
|
|
7607
|
+
"presence_update",
|
|
7608
|
+
"response_complete",
|
|
7609
|
+
"notification",
|
|
7610
|
+
"error"
|
|
7611
|
+
];
|
|
7049
7612
|
function registerWatchCommand(program2) {
|
|
7050
|
-
program2.command("watch").description("Watch workspace activity in real time").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-t, --timeout <seconds>", "Auto-close after N seconds").option("-n, --count <n>", "Stop after N messages").option(
|
|
7613
|
+
program2.command("watch").description("Watch workspace activity in real time").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-t, --timeout <seconds>", "Auto-close after N seconds").option("-n, --count <n>", "Stop after N messages").option(
|
|
7614
|
+
"--type <comma-list>",
|
|
7615
|
+
`Only emit events of these types (default: all). Known: ${KNOWN_WATCH_TYPES.join(",")}`
|
|
7616
|
+
).option("--json", "Output NDJSON (one JSON object per line)").action(
|
|
7051
7617
|
(opts) => runAction(async () => {
|
|
7052
7618
|
const { config, accessToken, workspaceId } = await resolveWorkspace(opts.workspace, {
|
|
7053
7619
|
skipNotifications: true
|
|
@@ -7055,6 +7621,21 @@ function registerWatchCommand(program2) {
|
|
|
7055
7621
|
const timeoutSec = opts.timeout ? parseInt(opts.timeout, 10) : void 0;
|
|
7056
7622
|
const maxCount = opts.count ? parseInt(opts.count, 10) : void 0;
|
|
7057
7623
|
const jsonMode = opts.json ?? false;
|
|
7624
|
+
const typeFilter = opts.type ? new Set(
|
|
7625
|
+
opts.type.split(",").map((t) => t.trim()).filter(Boolean)
|
|
7626
|
+
) : null;
|
|
7627
|
+
if (typeFilter) {
|
|
7628
|
+
const unknown = [...typeFilter].filter(
|
|
7629
|
+
(t) => !KNOWN_WATCH_TYPES.includes(t)
|
|
7630
|
+
);
|
|
7631
|
+
if (unknown.length > 0) {
|
|
7632
|
+
console.error(
|
|
7633
|
+
`Error: unknown event type(s): ${unknown.join(", ")}
|
|
7634
|
+
Known: ${KNOWN_WATCH_TYPES.join(", ")}`
|
|
7635
|
+
);
|
|
7636
|
+
process.exit(1);
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7058
7639
|
if (!jsonMode) {
|
|
7059
7640
|
const parts = [`Watching workspace ${workspaceId}...`];
|
|
7060
7641
|
if (timeoutSec) parts.push(`(timeout: ${timeoutSec}s)`);
|
|
@@ -7063,6 +7644,7 @@ function registerWatchCommand(program2) {
|
|
|
7063
7644
|
console.log(parts.join(" "));
|
|
7064
7645
|
}
|
|
7065
7646
|
let messageCount = 0;
|
|
7647
|
+
let timedOut = false;
|
|
7066
7648
|
let onDone;
|
|
7067
7649
|
const done = new Promise((r) => {
|
|
7068
7650
|
onDone = r;
|
|
@@ -7071,6 +7653,10 @@ function registerWatchCommand(program2) {
|
|
|
7071
7653
|
baseUrl: config.baseUrl,
|
|
7072
7654
|
accessToken,
|
|
7073
7655
|
onMessage: (msg) => {
|
|
7656
|
+
if (typeFilter) {
|
|
7657
|
+
const type = msg.type;
|
|
7658
|
+
if (!type || !typeFilter.has(type)) return;
|
|
7659
|
+
}
|
|
7074
7660
|
messageCount++;
|
|
7075
7661
|
if (jsonMode) {
|
|
7076
7662
|
console.log(JSON.stringify(msg));
|
|
@@ -7097,6 +7683,7 @@ Connection closed (code ${code}${reason ? ": " + reason : ""})`)
|
|
|
7097
7683
|
let timer;
|
|
7098
7684
|
if (timeoutSec) {
|
|
7099
7685
|
timer = setTimeout(() => {
|
|
7686
|
+
timedOut = true;
|
|
7100
7687
|
if (!jsonMode) console.log(chalk2__default.default.dim(`
|
|
7101
7688
|
Timeout (${timeoutSec}s), closing.`));
|
|
7102
7689
|
conn.close();
|
|
@@ -7110,21 +7697,53 @@ Timeout (${timeoutSec}s), closing.`));
|
|
|
7110
7697
|
await done;
|
|
7111
7698
|
if (timer) clearTimeout(timer);
|
|
7112
7699
|
process.removeListener("SIGINT", sigintHandler);
|
|
7700
|
+
if (timedOut && maxCount && messageCount < maxCount) {
|
|
7701
|
+
if (jsonMode) {
|
|
7702
|
+
console.log(
|
|
7703
|
+
JSON.stringify({
|
|
7704
|
+
type: "timeout",
|
|
7705
|
+
received: messageCount,
|
|
7706
|
+
expected: maxCount,
|
|
7707
|
+
timeout_seconds: timeoutSec
|
|
7708
|
+
})
|
|
7709
|
+
);
|
|
7710
|
+
} else {
|
|
7711
|
+
console.error(
|
|
7712
|
+
chalk2__default.default.yellow(`Timed out before reaching ${maxCount} messages (got ${messageCount}).`)
|
|
7713
|
+
);
|
|
7714
|
+
}
|
|
7715
|
+
process.exit(124);
|
|
7716
|
+
}
|
|
7113
7717
|
})()
|
|
7114
7718
|
);
|
|
7115
7719
|
}
|
|
7720
|
+
init_prompts();
|
|
7721
|
+
function redactPictures(rows) {
|
|
7722
|
+
return rows.map((row) => {
|
|
7723
|
+
const user = row.user;
|
|
7724
|
+
if (!user || typeof user.picture !== "string" || user.picture.length === 0) return row;
|
|
7725
|
+
return {
|
|
7726
|
+
...row,
|
|
7727
|
+
user: {
|
|
7728
|
+
...user,
|
|
7729
|
+
has_picture: true,
|
|
7730
|
+
picture: null
|
|
7731
|
+
}
|
|
7732
|
+
};
|
|
7733
|
+
});
|
|
7734
|
+
}
|
|
7116
7735
|
function registerContactsCommand(program2) {
|
|
7117
|
-
const contacts = program2.command("contacts").description("
|
|
7118
|
-
contacts.command("list").description("List all contacts").option("--json", "Output as JSON").action(
|
|
7736
|
+
const contacts = program2.command("contacts").description("Contacts: list, add, remove");
|
|
7737
|
+
contacts.command("list").description("List all contacts").option("--json", "Output as JSON").option("--include-pictures", "Keep base64 profile-picture bytes in --json output").action(
|
|
7119
7738
|
(opts) => runAction(async () => {
|
|
7120
7739
|
const { arbi } = await resolveAuth();
|
|
7121
7740
|
const data = await sdk.contacts.listContacts(arbi);
|
|
7122
7741
|
if (opts.json) {
|
|
7123
|
-
|
|
7742
|
+
printJson(opts.includePictures ? data : redactPictures(data));
|
|
7124
7743
|
return;
|
|
7125
7744
|
}
|
|
7126
7745
|
if (data.length === 0) {
|
|
7127
|
-
|
|
7746
|
+
process.stderr.write("No contacts found.\n");
|
|
7128
7747
|
return;
|
|
7129
7748
|
}
|
|
7130
7749
|
printTable(
|
|
@@ -7146,6 +7765,7 @@ function registerContactsCommand(program2) {
|
|
|
7146
7765
|
(emails) => runAction(async () => {
|
|
7147
7766
|
const { arbi } = await resolveAuth();
|
|
7148
7767
|
if (!emails || emails.length === 0) {
|
|
7768
|
+
requireInteractive("Pass email(s) as positional args: arbi contacts add foo@x.y bar@x.y");
|
|
7149
7769
|
const input2 = await promptInput("Email address(es), comma-separated");
|
|
7150
7770
|
emails = input2.split(",").map((e) => e.trim()).filter(Boolean);
|
|
7151
7771
|
if (emails.length === 0) return;
|
|
@@ -7156,16 +7776,17 @@ function registerContactsCommand(program2) {
|
|
|
7156
7776
|
}
|
|
7157
7777
|
})()
|
|
7158
7778
|
);
|
|
7159
|
-
contacts.command("remove [ids...]").description("Remove contacts (interactive picker if no IDs given)").action(
|
|
7160
|
-
(ids) => runAction(async () => {
|
|
7779
|
+
contacts.command("remove [ids...]").description("Remove contacts (interactive picker if no IDs given)").option("--dry-run", "Preview which contacts would be removed (no SDK call)").action(
|
|
7780
|
+
(ids, opts) => runAction(async () => {
|
|
7161
7781
|
const { arbi } = await resolveAuth();
|
|
7162
7782
|
let contactIds = ids && ids.length > 0 ? ids : void 0;
|
|
7163
7783
|
if (!contactIds) {
|
|
7164
7784
|
const data = await sdk.contacts.listContacts(arbi);
|
|
7165
7785
|
if (data.length === 0) {
|
|
7166
|
-
|
|
7786
|
+
process.stderr.write("No contacts found.\n");
|
|
7167
7787
|
return;
|
|
7168
7788
|
}
|
|
7789
|
+
requireInteractive("Pass contact IDs directly: arbi contacts remove cnt-\u2026");
|
|
7169
7790
|
contactIds = await promptCheckbox(
|
|
7170
7791
|
"Select contacts to remove",
|
|
7171
7792
|
data.map((c) => {
|
|
@@ -7178,57 +7799,157 @@ function registerContactsCommand(program2) {
|
|
|
7178
7799
|
);
|
|
7179
7800
|
if (contactIds.length === 0) return;
|
|
7180
7801
|
}
|
|
7802
|
+
if (opts?.dryRun) {
|
|
7803
|
+
dryRun(`remove ${contactIds.length} contact(s)`, contactIds);
|
|
7804
|
+
return;
|
|
7805
|
+
}
|
|
7181
7806
|
await sdk.contacts.removeContacts(arbi, contactIds);
|
|
7182
7807
|
success(`Removed ${contactIds.length} contact(s).`);
|
|
7183
7808
|
})()
|
|
7184
7809
|
);
|
|
7185
|
-
contacts.
|
|
7186
|
-
const
|
|
7187
|
-
|
|
7188
|
-
|
|
7810
|
+
contacts.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
7811
|
+
const tail = contacts.args ?? [];
|
|
7812
|
+
await contacts.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
7813
|
+
});
|
|
7814
|
+
}
|
|
7815
|
+
init_prompts();
|
|
7816
|
+
async function resolveRecipient(arbi, ref2) {
|
|
7817
|
+
const isEmail = ref2.includes("@");
|
|
7818
|
+
const matchKey = isEmail ? "email" : "external_id";
|
|
7819
|
+
const contacts = await sdk.contacts.listContacts(arbi);
|
|
7820
|
+
const contactMatch = contacts.find((c) => {
|
|
7821
|
+
if (isEmail) return c.email === ref2;
|
|
7822
|
+
const u = c.user;
|
|
7823
|
+
return u?.external_id === ref2 || c.external_id === ref2;
|
|
7189
7824
|
});
|
|
7825
|
+
if (contactMatch) {
|
|
7826
|
+
const u = contactMatch.user;
|
|
7827
|
+
const extId = u?.external_id ?? contactMatch.external_id;
|
|
7828
|
+
const pubKey = u?.encryption_public_key ?? "";
|
|
7829
|
+
if (pubKey) return { extId, pubKey };
|
|
7830
|
+
}
|
|
7831
|
+
try {
|
|
7832
|
+
const agents = await sdk.agents.listAgents(arbi);
|
|
7833
|
+
const agentMatch = agents.find((a) => a[matchKey] === ref2);
|
|
7834
|
+
if (agentMatch) {
|
|
7835
|
+
return { extId: agentMatch.external_id, pubKey: agentMatch.encryption_public_key };
|
|
7836
|
+
}
|
|
7837
|
+
} catch {
|
|
7838
|
+
}
|
|
7839
|
+
try {
|
|
7840
|
+
const wsUsers = await sdk.workspaces.listWorkspaceUsers(arbi);
|
|
7841
|
+
const wsMatch = wsUsers.find((wu) => wu[matchKey] === ref2);
|
|
7842
|
+
if (wsMatch) {
|
|
7843
|
+
const u = wsMatch;
|
|
7844
|
+
return {
|
|
7845
|
+
extId: u.external_id,
|
|
7846
|
+
pubKey: u.encryption_public_key
|
|
7847
|
+
};
|
|
7848
|
+
}
|
|
7849
|
+
} catch {
|
|
7850
|
+
}
|
|
7851
|
+
return null;
|
|
7852
|
+
}
|
|
7853
|
+
function rowMatchesFilters(row, opts) {
|
|
7854
|
+
if (opts.unread && row.read) return false;
|
|
7855
|
+
const sender = row.sender;
|
|
7856
|
+
const recipient = row.recipient;
|
|
7857
|
+
if (opts.from) {
|
|
7858
|
+
const needle = opts.from.toLowerCase();
|
|
7859
|
+
const senderMatch = sender?.email?.toLowerCase() === needle || sender?.external_id === opts.from;
|
|
7860
|
+
const recipientMatch = recipient?.email?.toLowerCase() === needle || recipient?.external_id === opts.from;
|
|
7861
|
+
if (!senderMatch && !recipientMatch) return false;
|
|
7862
|
+
}
|
|
7863
|
+
if (opts.thread) {
|
|
7864
|
+
const needle = opts.thread.toLowerCase();
|
|
7865
|
+
const peer = sender?.external_id === opts.myExtId ? recipient : sender;
|
|
7866
|
+
const peerMatch = peer?.email?.toLowerCase() === needle || peer?.external_id === opts.thread;
|
|
7867
|
+
if (!peerMatch) return false;
|
|
7868
|
+
}
|
|
7869
|
+
if (opts.since) {
|
|
7870
|
+
const cutoff = Date.parse(opts.since);
|
|
7871
|
+
if (Number.isFinite(cutoff)) {
|
|
7872
|
+
const createdAt = typeof row.created_at === "string" ? Date.parse(row.created_at) : NaN;
|
|
7873
|
+
if (!Number.isFinite(createdAt) || createdAt < cutoff) return false;
|
|
7874
|
+
}
|
|
7875
|
+
}
|
|
7876
|
+
return true;
|
|
7190
7877
|
}
|
|
7191
7878
|
function registerDmCommand(program2) {
|
|
7192
7879
|
const dm = program2.command("dm").description("Direct messages (E2E encrypted)");
|
|
7193
|
-
dm.command("list").description("List
|
|
7194
|
-
|
|
7880
|
+
dm.command("list").description("List direct messages (decrypted, newest first)").option("--unread", "Only show unread messages").option("--from <email-or-extid>", "Filter by sender OR recipient (email or usr- id)").option(
|
|
7881
|
+
"--thread <email-or-extid>",
|
|
7882
|
+
"Show the full conversation with one peer (both directions)"
|
|
7883
|
+
).option("--since <iso>", "Only messages newer than this ISO timestamp").option("-l, --limit <n>", "Cap to N most-recent messages", (v) => parseInt(v, 10)).option("--json", "Output as JSON (recommended for scripting / agents)").action(
|
|
7884
|
+
(opts) => runAction(async () => {
|
|
7195
7885
|
const { arbi, crypto: crypto2 } = await resolveDmCrypto();
|
|
7196
|
-
const
|
|
7197
|
-
|
|
7198
|
-
|
|
7886
|
+
const all = await sdk.dm.listDecryptedDMs(arbi, crypto2);
|
|
7887
|
+
const myExtId = arbi.session.getState().userExtId;
|
|
7888
|
+
const dms = all.filter((r) => r.type === "user_message");
|
|
7889
|
+
const filterOpts = {
|
|
7890
|
+
unread: Boolean(opts.unread),
|
|
7891
|
+
from: typeof opts.from === "string" ? opts.from : void 0,
|
|
7892
|
+
thread: typeof opts.thread === "string" ? opts.thread : void 0,
|
|
7893
|
+
since: typeof opts.since === "string" ? opts.since : void 0,
|
|
7894
|
+
myExtId
|
|
7895
|
+
};
|
|
7896
|
+
const filtered = dms.filter((r) => rowMatchesFilters(r, filterOpts));
|
|
7897
|
+
const limited = typeof opts.limit === "number" && opts.limit > 0 ? filtered.slice(0, opts.limit) : filtered;
|
|
7898
|
+
if (opts.json) {
|
|
7899
|
+
printJson(limited.map((r) => ({ ...r, encrypted_in_transit: true })));
|
|
7900
|
+
return;
|
|
7901
|
+
}
|
|
7902
|
+
if (limited.length === 0) {
|
|
7903
|
+
process.stderr.write("No messages found.\n");
|
|
7199
7904
|
return;
|
|
7200
7905
|
}
|
|
7906
|
+
const peer = (r) => {
|
|
7907
|
+
const s = r.sender;
|
|
7908
|
+
const recip = r.recipient;
|
|
7909
|
+
return s?.external_id === myExtId ? recip : s;
|
|
7910
|
+
};
|
|
7201
7911
|
printTable(
|
|
7202
7912
|
[
|
|
7203
7913
|
{ header: "ID", width: 16, value: (r) => r.external_id },
|
|
7204
|
-
{ header: "TYPE", width: 18, value: (r) => r.type },
|
|
7205
7914
|
{
|
|
7206
|
-
header: "
|
|
7207
|
-
width:
|
|
7915
|
+
header: "DIR",
|
|
7916
|
+
width: 4,
|
|
7917
|
+
value: (r) => r.sender?.external_id === myExtId ? "\u2192" : "\u2190"
|
|
7918
|
+
},
|
|
7919
|
+
{
|
|
7920
|
+
header: "PEER",
|
|
7921
|
+
width: 26,
|
|
7208
7922
|
value: (r) => {
|
|
7209
|
-
const
|
|
7210
|
-
return sdk.formatUserName(
|
|
7923
|
+
const p = peer(r);
|
|
7924
|
+
return sdk.formatUserName(p) || p?.email || "";
|
|
7211
7925
|
}
|
|
7212
7926
|
},
|
|
7213
7927
|
{ header: "READ", width: 6, value: (r) => r.read ? "yes" : "no" },
|
|
7214
|
-
{
|
|
7928
|
+
{
|
|
7929
|
+
header: "CONTENT",
|
|
7930
|
+
width: 50,
|
|
7931
|
+
value: (r) => truncate(r.content ?? "", 49)
|
|
7932
|
+
}
|
|
7215
7933
|
],
|
|
7216
|
-
|
|
7934
|
+
limited
|
|
7217
7935
|
);
|
|
7218
|
-
})
|
|
7936
|
+
})()
|
|
7219
7937
|
);
|
|
7220
7938
|
dm.command("send [recipient] [content...]").description("Send an E2E encrypted DM (interactive if no args)").action(
|
|
7221
7939
|
(recipient, contentParts) => runAction(async () => {
|
|
7222
7940
|
const { arbi, crypto: crypto2 } = await resolveDmCrypto();
|
|
7223
7941
|
if (!recipient) {
|
|
7224
|
-
|
|
7225
|
-
|
|
7942
|
+
requireInteractive(
|
|
7943
|
+
'Pass recipient + message as arguments: arbi dm send <email-or-id> "<message>"'
|
|
7944
|
+
);
|
|
7945
|
+
const contacts = await sdk.contacts.listContacts(arbi);
|
|
7946
|
+
if (contacts.length === 0) {
|
|
7226
7947
|
error("No contacts found. Add contacts first: arbi contacts add <email>");
|
|
7227
7948
|
process.exit(1);
|
|
7228
7949
|
}
|
|
7229
7950
|
recipient = await promptSelect(
|
|
7230
7951
|
"Send to",
|
|
7231
|
-
|
|
7952
|
+
contacts.map((c) => {
|
|
7232
7953
|
const u = c.user;
|
|
7233
7954
|
const name = sdk.formatUserName(u);
|
|
7234
7955
|
return {
|
|
@@ -7241,79 +7962,26 @@ function registerDmCommand(program2) {
|
|
|
7241
7962
|
}
|
|
7242
7963
|
let content = contentParts?.length ? contentParts.join(" ") : void 0;
|
|
7243
7964
|
if (!content) {
|
|
7965
|
+
requireInteractive(
|
|
7966
|
+
'Pass the message as a positional argument: arbi dm send <email> "your message"'
|
|
7967
|
+
);
|
|
7244
7968
|
content = await promptInput("Message");
|
|
7245
7969
|
}
|
|
7246
|
-
const
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
recipientExtId = u?.external_id ?? match.external_id;
|
|
7254
|
-
recipientPubKey = u?.encryption_public_key;
|
|
7255
|
-
} else {
|
|
7256
|
-
try {
|
|
7257
|
-
const agentsList = await sdk.agents.listAgents(arbi);
|
|
7258
|
-
const agentMatch = agentsList.find((a) => a.email === recipient);
|
|
7259
|
-
if (agentMatch) {
|
|
7260
|
-
recipientExtId = agentMatch.external_id;
|
|
7261
|
-
recipientPubKey = agentMatch.encryption_public_key;
|
|
7262
|
-
}
|
|
7263
|
-
} catch {
|
|
7264
|
-
}
|
|
7265
|
-
if (!recipientPubKey) {
|
|
7266
|
-
try {
|
|
7267
|
-
const wsUsers = await sdk.workspaces.listWorkspaceUsers(arbi);
|
|
7268
|
-
const wsMatch = wsUsers.find(
|
|
7269
|
-
(wu) => wu.email === recipient
|
|
7270
|
-
);
|
|
7271
|
-
if (wsMatch) {
|
|
7272
|
-
const u = wsMatch;
|
|
7273
|
-
recipientExtId = u.external_id;
|
|
7274
|
-
recipientPubKey = u.encryption_public_key;
|
|
7275
|
-
}
|
|
7276
|
-
} catch {
|
|
7277
|
-
}
|
|
7278
|
-
}
|
|
7279
|
-
if (!recipientExtId || recipientExtId === recipient) {
|
|
7280
|
-
error(`No contact, agent, or workspace member found with email: ${recipient}`);
|
|
7281
|
-
process.exit(1);
|
|
7282
|
-
}
|
|
7283
|
-
}
|
|
7284
|
-
} else {
|
|
7285
|
-
const match = contacts.find((c) => {
|
|
7286
|
-
const u = c.user;
|
|
7287
|
-
return u?.external_id === recipient || c.external_id === recipient;
|
|
7288
|
-
});
|
|
7289
|
-
recipientPubKey = match?.user?.encryption_public_key;
|
|
7290
|
-
if (!recipientPubKey) {
|
|
7291
|
-
try {
|
|
7292
|
-
const agentsList = await sdk.agents.listAgents(arbi);
|
|
7293
|
-
const agentMatch = agentsList.find((a) => a.external_id === recipientExtId);
|
|
7294
|
-
if (agentMatch) {
|
|
7295
|
-
recipientPubKey = agentMatch.encryption_public_key;
|
|
7296
|
-
}
|
|
7297
|
-
} catch {
|
|
7298
|
-
}
|
|
7299
|
-
}
|
|
7300
|
-
if (!recipientPubKey) {
|
|
7301
|
-
try {
|
|
7302
|
-
const wsUsers = await sdk.workspaces.listWorkspaceUsers(arbi);
|
|
7303
|
-
const wsMatch = wsUsers.find(
|
|
7304
|
-
(wu) => wu.external_id === recipientExtId
|
|
7305
|
-
);
|
|
7306
|
-
if (wsMatch) {
|
|
7307
|
-
recipientPubKey = wsMatch.encryption_public_key;
|
|
7308
|
-
}
|
|
7309
|
-
} catch {
|
|
7310
|
-
}
|
|
7311
|
-
}
|
|
7970
|
+
const resolved = await resolveRecipient(arbi, recipient);
|
|
7971
|
+
if (!resolved) {
|
|
7972
|
+
error(
|
|
7973
|
+
`No contact, agent, or workspace member found for: ${recipient}
|
|
7974
|
+
Try: arbi contacts add ${recipient.includes("@") ? recipient : "<their-email>"}`
|
|
7975
|
+
);
|
|
7976
|
+
process.exit(3);
|
|
7312
7977
|
}
|
|
7978
|
+
const recipientExtId = resolved.extId;
|
|
7979
|
+
const recipientPubKey = resolved.pubKey;
|
|
7313
7980
|
if (!recipientPubKey) {
|
|
7314
|
-
error(
|
|
7315
|
-
|
|
7316
|
-
|
|
7981
|
+
error(
|
|
7982
|
+
"Cannot send encrypted DM \u2014 recipient public key not found.\nAdd them as a contact first: arbi contacts add <email>"
|
|
7983
|
+
);
|
|
7984
|
+
process.exit(3);
|
|
7317
7985
|
}
|
|
7318
7986
|
const data = await sdk.dm.sendEncryptedDM(
|
|
7319
7987
|
arbi,
|
|
@@ -7327,21 +7995,26 @@ function registerDmCommand(program2) {
|
|
|
7327
7995
|
crypto2
|
|
7328
7996
|
);
|
|
7329
7997
|
for (const n of data) {
|
|
7330
|
-
success(`Sent: ${n.external_id} \u2192 ${n.recipient.email}`);
|
|
7998
|
+
success(`Sent (encrypted): ${n.external_id} \u2192 ${n.recipient.email}`);
|
|
7331
7999
|
}
|
|
7332
8000
|
})()
|
|
7333
8001
|
);
|
|
7334
|
-
dm.command("read [ids...]").description("Mark messages as read (interactive picker if no IDs given)").action(
|
|
7335
|
-
(ids) => runAction(async () => {
|
|
8002
|
+
dm.command("read [ids...]").description("Mark messages as read (interactive picker if no IDs given)").option("--all", "Mark every unread message as read (no picker)").action(
|
|
8003
|
+
(ids, opts) => runAction(async () => {
|
|
7336
8004
|
const { arbi, crypto: crypto2 } = await resolveDmCrypto();
|
|
7337
8005
|
let msgIds = ids && ids.length > 0 ? ids : void 0;
|
|
8006
|
+
if (!msgIds && opts?.all) {
|
|
8007
|
+
const data2 = await sdk.dm.listDecryptedDMs(arbi, crypto2);
|
|
8008
|
+
msgIds = data2.filter((m) => !m.read).map((m) => m.external_id);
|
|
8009
|
+
}
|
|
7338
8010
|
if (!msgIds) {
|
|
7339
8011
|
const data2 = await sdk.dm.listDecryptedDMs(arbi, crypto2);
|
|
7340
8012
|
const unread = data2.filter((m) => !m.read);
|
|
7341
8013
|
if (unread.length === 0) {
|
|
7342
|
-
|
|
8014
|
+
process.stderr.write("No unread messages.\n");
|
|
7343
8015
|
return;
|
|
7344
8016
|
}
|
|
8017
|
+
requireInteractive("Pass IDs directly or use --all to mark every unread message read.");
|
|
7345
8018
|
msgIds = await promptCheckbox(
|
|
7346
8019
|
"Select messages to mark as read",
|
|
7347
8020
|
unread.map((m) => {
|
|
@@ -7355,20 +8028,29 @@ function registerDmCommand(program2) {
|
|
|
7355
8028
|
);
|
|
7356
8029
|
if (msgIds.length === 0) return;
|
|
7357
8030
|
}
|
|
8031
|
+
if (msgIds.length === 0) {
|
|
8032
|
+
process.stderr.write("No unread messages.\n");
|
|
8033
|
+
return;
|
|
8034
|
+
}
|
|
7358
8035
|
const data = await sdk.dm.markRead(arbi, msgIds);
|
|
7359
8036
|
success(`Marked ${data.length} message(s) as read.`);
|
|
7360
8037
|
})()
|
|
7361
8038
|
);
|
|
7362
|
-
dm.command("delete [ids...]").description("Delete messages (interactive picker if no IDs given)").action(
|
|
7363
|
-
(ids) => runAction(async () => {
|
|
8039
|
+
dm.command("delete [ids...]").description("Delete messages (interactive picker if no IDs given)").option("--all", "Delete every message in your DM history (no picker, no prompt)").option("--dry-run", "Preview which messages would be deleted (no SDK call)").action(
|
|
8040
|
+
(ids, opts) => runAction(async () => {
|
|
7364
8041
|
const { arbi, crypto: crypto2 } = await resolveDmCrypto();
|
|
7365
8042
|
let msgIds = ids && ids.length > 0 ? ids : void 0;
|
|
8043
|
+
if (!msgIds && opts?.all) {
|
|
8044
|
+
const data = await sdk.dm.listDecryptedDMs(arbi, crypto2);
|
|
8045
|
+
msgIds = data.map((m) => m.external_id);
|
|
8046
|
+
}
|
|
7366
8047
|
if (!msgIds) {
|
|
7367
8048
|
const data = await sdk.dm.listDecryptedDMs(arbi, crypto2);
|
|
7368
8049
|
if (data.length === 0) {
|
|
7369
|
-
|
|
8050
|
+
process.stderr.write("No messages found.\n");
|
|
7370
8051
|
return;
|
|
7371
8052
|
}
|
|
8053
|
+
requireInteractive("Pass IDs directly or use --all to delete the whole inbox.");
|
|
7372
8054
|
msgIds = await promptCheckbox(
|
|
7373
8055
|
"Select messages to delete",
|
|
7374
8056
|
data.map((m) => {
|
|
@@ -7382,18 +8064,34 @@ function registerDmCommand(program2) {
|
|
|
7382
8064
|
);
|
|
7383
8065
|
if (msgIds.length === 0) return;
|
|
7384
8066
|
}
|
|
8067
|
+
if (msgIds.length === 0) {
|
|
8068
|
+
process.stderr.write("No messages to delete.\n");
|
|
8069
|
+
return;
|
|
8070
|
+
}
|
|
8071
|
+
if (opts?.dryRun) {
|
|
8072
|
+
dryRun(`delete ${msgIds.length} message(s)`, msgIds);
|
|
8073
|
+
return;
|
|
8074
|
+
}
|
|
7385
8075
|
await sdk.dm.deleteDMs(arbi, msgIds);
|
|
7386
8076
|
success(`Deleted ${msgIds.length} message(s).`);
|
|
7387
8077
|
})()
|
|
7388
8078
|
);
|
|
7389
|
-
dm.action(async () => {
|
|
8079
|
+
dm.arguments("[maybeSubcommand]").action(async (maybe) => {
|
|
8080
|
+
if (maybe) {
|
|
8081
|
+
suggestSubcommandAndExit(
|
|
8082
|
+
"dm",
|
|
8083
|
+
maybe,
|
|
8084
|
+
dm.commands.map((c) => c.name())
|
|
8085
|
+
);
|
|
8086
|
+
}
|
|
7390
8087
|
await dm.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
|
|
7391
8088
|
});
|
|
7392
8089
|
}
|
|
8090
|
+
init_prompts();
|
|
7393
8091
|
async function fetchTagChoices(arbi) {
|
|
7394
8092
|
const data = await sdk.tags.listTags(arbi);
|
|
7395
8093
|
if (data.length === 0) {
|
|
7396
|
-
|
|
8094
|
+
process.stderr.write("No tags found.\n");
|
|
7397
8095
|
process.exit(0);
|
|
7398
8096
|
}
|
|
7399
8097
|
return {
|
|
@@ -7406,17 +8104,17 @@ async function fetchTagChoices(arbi) {
|
|
|
7406
8104
|
};
|
|
7407
8105
|
}
|
|
7408
8106
|
function registerTagsCommand(program2) {
|
|
7409
|
-
const tags = program2.command("tags").description("
|
|
8107
|
+
const tags = program2.command("tags").description("Tags: list, create, delete, update, share, unshare, rename");
|
|
7410
8108
|
tags.command("list").description("List tags in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output as JSON").action(
|
|
7411
8109
|
(opts) => runAction(async () => {
|
|
7412
8110
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
7413
8111
|
const data = await sdk.tags.listTags(arbi);
|
|
7414
8112
|
if (opts.json) {
|
|
7415
|
-
|
|
8113
|
+
printJson(data);
|
|
7416
8114
|
return;
|
|
7417
8115
|
}
|
|
7418
8116
|
if (data.length === 0) {
|
|
7419
|
-
|
|
8117
|
+
process.stderr.write("No tags found.\n");
|
|
7420
8118
|
return;
|
|
7421
8119
|
}
|
|
7422
8120
|
printTable(
|
|
@@ -7470,17 +8168,42 @@ function registerTagsCommand(program2) {
|
|
|
7470
8168
|
success(`Created: ${data.name} (${ref(data.external_id)})`);
|
|
7471
8169
|
})()
|
|
7472
8170
|
);
|
|
7473
|
-
tags.command("delete [id]").description("Delete a tag (interactive picker if no ID given)").action(
|
|
7474
|
-
(id) => runAction(async () => {
|
|
8171
|
+
tags.command("delete [id]").description("Delete a tag (interactive picker if no ID given)").option("--dry-run", "Preview which tag would be deleted (no SDK call)").action(
|
|
8172
|
+
(id, opts) => runAction(async () => {
|
|
7475
8173
|
const { arbi } = await resolveWorkspace();
|
|
7476
8174
|
if (!id) {
|
|
7477
8175
|
const { choices } = await fetchTagChoices(arbi);
|
|
7478
8176
|
id = await promptSelect("Select tag to delete", choices);
|
|
7479
8177
|
}
|
|
8178
|
+
if (opts?.dryRun) {
|
|
8179
|
+
dryRun("delete tag", id);
|
|
8180
|
+
return;
|
|
8181
|
+
}
|
|
7480
8182
|
const data = await sdk.tags.deleteTag(arbi, id);
|
|
7481
8183
|
success(data?.detail ?? `Deleted tag ${id}`);
|
|
7482
8184
|
})()
|
|
7483
8185
|
);
|
|
8186
|
+
tags.command("share <id>").description("Mark a tag as shared (visible to other workspace members)").action(
|
|
8187
|
+
(id) => runAction(async () => {
|
|
8188
|
+
const { arbi } = await resolveWorkspace();
|
|
8189
|
+
const data = await sdk.tags.updateTag(arbi, id, { shared: true });
|
|
8190
|
+
success(`Shared: ${data.name} (${ref(data.external_id)})`);
|
|
8191
|
+
})()
|
|
8192
|
+
);
|
|
8193
|
+
tags.command("unshare <id>").description("Mark a tag as private (owner-only)").action(
|
|
8194
|
+
(id) => runAction(async () => {
|
|
8195
|
+
const { arbi } = await resolveWorkspace();
|
|
8196
|
+
const data = await sdk.tags.updateTag(arbi, id, { shared: false });
|
|
8197
|
+
success(`Unshared: ${data.name} (${ref(data.external_id)})`);
|
|
8198
|
+
})()
|
|
8199
|
+
);
|
|
8200
|
+
tags.command("rename <id> <name>").description("Rename a tag").action(
|
|
8201
|
+
(id, name) => runAction(async () => {
|
|
8202
|
+
const { arbi } = await resolveWorkspace();
|
|
8203
|
+
const data = await sdk.tags.updateTag(arbi, id, { name });
|
|
8204
|
+
success(`Renamed: ${data.name} (${ref(data.external_id)})`);
|
|
8205
|
+
})()
|
|
8206
|
+
);
|
|
7484
8207
|
tags.command("update [id] [json]").description("Update a tag (interactive picker + form if no args)").action(
|
|
7485
8208
|
(id, json) => runAction(async () => {
|
|
7486
8209
|
const { arbi } = await resolveWorkspace();
|
|
@@ -7507,34 +8230,43 @@ function registerTagsCommand(program2) {
|
|
|
7507
8230
|
success(`Updated: ${data.name} (${ref(data.external_id)})`);
|
|
7508
8231
|
})()
|
|
7509
8232
|
);
|
|
7510
|
-
tags.
|
|
7511
|
-
const
|
|
7512
|
-
|
|
7513
|
-
if (opts.workspace) args.push("-w", opts.workspace);
|
|
7514
|
-
await cmd.commands.find((c) => c.name() === "list").parseAsync(args, { from: "user" });
|
|
8233
|
+
tags.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
8234
|
+
const tail = tags.args ?? [];
|
|
8235
|
+
await tags.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
7515
8236
|
});
|
|
7516
8237
|
}
|
|
7517
|
-
|
|
8238
|
+
init_prompts();
|
|
8239
|
+
async function resolveTagRef(arbi, ref2) {
|
|
8240
|
+
if (ref2.startsWith("tag-")) return ref2;
|
|
7518
8241
|
const data = await sdk.tags.listTags(arbi);
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
data
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
8242
|
+
const res = resolveByIdOrName(data, ref2);
|
|
8243
|
+
if (!res.ok) failResolveAndExit("tag", ref2, res);
|
|
8244
|
+
return res.id;
|
|
8245
|
+
}
|
|
8246
|
+
function pickTag(arbi, message = "Select tag") {
|
|
8247
|
+
return sdk.tags.listTags(arbi).then(
|
|
8248
|
+
(data) => pickFromList(
|
|
8249
|
+
data,
|
|
8250
|
+
(t) => ({
|
|
8251
|
+
name: `${t.name} (${t.tag_type?.type ?? ""})`,
|
|
8252
|
+
value: t.external_id,
|
|
8253
|
+
description: t.external_id
|
|
8254
|
+
}),
|
|
8255
|
+
{
|
|
8256
|
+
message,
|
|
8257
|
+
emptyMessage: "No tags found.",
|
|
8258
|
+
nonTtyHint: "Pass <tag-id> (or unique tag name) and <doc-ids...> directly."
|
|
8259
|
+
}
|
|
8260
|
+
)
|
|
7530
8261
|
);
|
|
7531
8262
|
}
|
|
7532
|
-
async function pickDocs(arbi,
|
|
8263
|
+
async function pickDocs(arbi, message = "Select documents") {
|
|
7533
8264
|
const data = await sdk.documents.listDocuments(arbi);
|
|
7534
8265
|
if (data.length === 0) {
|
|
7535
|
-
|
|
8266
|
+
process.stderr.write("No documents found.\n");
|
|
7536
8267
|
process.exit(0);
|
|
7537
8268
|
}
|
|
8269
|
+
requireInteractive("Pass <doc-ids...> directly: arbi docs --ids to list them.");
|
|
7538
8270
|
return promptCheckbox(
|
|
7539
8271
|
message,
|
|
7540
8272
|
data.map((d) => ({
|
|
@@ -7544,32 +8276,37 @@ async function pickDocs(arbi, workspaceId, message = "Select documents") {
|
|
|
7544
8276
|
);
|
|
7545
8277
|
}
|
|
7546
8278
|
function registerDoctagsCommand(program2) {
|
|
7547
|
-
const doctagsCmd = program2.command("doctags").description("
|
|
7548
|
-
doctagsCmd.command("create [tag
|
|
7549
|
-
(
|
|
7550
|
-
const { arbi
|
|
7551
|
-
|
|
7552
|
-
if (!
|
|
7553
|
-
|
|
8279
|
+
const doctagsCmd = program2.command("doctags").description("Doctags (tag\u2194doc links): create, delete, generate");
|
|
8280
|
+
doctagsCmd.command("create [tag] [doc-ids...]").description("Assign a tag to documents (tag accepts ID or unique name; pickers if no args)").option("-n, --note <text>", "Note for the doctag").action(
|
|
8281
|
+
(tagRef, docIds, opts) => runAction(async () => {
|
|
8282
|
+
const { arbi } = await resolveWorkspace();
|
|
8283
|
+
let tagId = tagRef ? await resolveTagRef(arbi, tagRef) : void 0;
|
|
8284
|
+
if (!tagId) tagId = await pickTag(arbi, "Select tag to assign");
|
|
8285
|
+
if (!docIds || docIds.length === 0) docIds = await pickDocs(arbi, "Select documents to tag");
|
|
7554
8286
|
if (docIds.length === 0) return;
|
|
7555
8287
|
const data = await sdk.doctags.assignDocTags(arbi, tagId, docIds, opts?.note);
|
|
7556
8288
|
success(`Created ${data.length} doctag(s) for tag ${tagId}.`);
|
|
7557
8289
|
})()
|
|
7558
8290
|
);
|
|
7559
|
-
doctagsCmd.command("delete [tag
|
|
7560
|
-
(
|
|
7561
|
-
const { arbi
|
|
7562
|
-
|
|
8291
|
+
doctagsCmd.command("delete [tag] [doc-ids...]").description("Remove a tag from documents (tag accepts ID or unique name; pickers if no args)").option("--dry-run", "Preview which doctag links would be removed (no SDK call)").action(
|
|
8292
|
+
(tagRef, docIds, opts) => runAction(async () => {
|
|
8293
|
+
const { arbi } = await resolveWorkspace();
|
|
8294
|
+
let tagId = tagRef ? await resolveTagRef(arbi, tagRef) : void 0;
|
|
8295
|
+
if (!tagId) tagId = await pickTag(arbi, "Select tag to remove");
|
|
7563
8296
|
if (!docIds || docIds.length === 0)
|
|
7564
|
-
docIds = await pickDocs(arbi,
|
|
8297
|
+
docIds = await pickDocs(arbi, "Select documents to untag");
|
|
7565
8298
|
if (docIds.length === 0) return;
|
|
8299
|
+
if (opts?.dryRun) {
|
|
8300
|
+
dryRun(`remove tag ${tagId} from ${docIds.length} document(s)`, docIds);
|
|
8301
|
+
return;
|
|
8302
|
+
}
|
|
7566
8303
|
await sdk.doctags.removeDocTags(arbi, tagId, docIds);
|
|
7567
8304
|
success(`Removed tag ${tagId} from ${docIds.length} document(s).`);
|
|
7568
8305
|
})()
|
|
7569
8306
|
);
|
|
7570
8307
|
doctagsCmd.command("generate").description("AI-generate doctags (interactive pickers if no flags)").option("--tags <ids>", "Comma-separated tag IDs").option("--docs <ids>", "Comma-separated document IDs").action(
|
|
7571
8308
|
(opts) => runAction(async () => {
|
|
7572
|
-
const { arbi
|
|
8309
|
+
const { arbi } = await resolveWorkspace();
|
|
7573
8310
|
let tagIds;
|
|
7574
8311
|
if (opts.tags) {
|
|
7575
8312
|
tagIds = opts.tags.split(",").map((s) => s.trim());
|
|
@@ -7592,7 +8329,7 @@ function registerDoctagsCommand(program2) {
|
|
|
7592
8329
|
if (opts.docs) {
|
|
7593
8330
|
docIds = opts.docs.split(",").map((s) => s.trim());
|
|
7594
8331
|
} else {
|
|
7595
|
-
docIds = await pickDocs(arbi,
|
|
8332
|
+
docIds = await pickDocs(arbi, "Select documents for AI tagging");
|
|
7596
8333
|
if (docIds.length === 0) return;
|
|
7597
8334
|
}
|
|
7598
8335
|
const data = await sdk.doctags.generateDocTags(arbi, tagIds, docIds);
|
|
@@ -7602,10 +8339,11 @@ function registerDoctagsCommand(program2) {
|
|
|
7602
8339
|
})()
|
|
7603
8340
|
);
|
|
7604
8341
|
}
|
|
8342
|
+
init_prompts();
|
|
7605
8343
|
async function pickConversation(arbi, workspaceId, message = "Select conversation") {
|
|
7606
8344
|
const data = await sdk.conversations.listConversations(arbi);
|
|
7607
8345
|
if (data.length === 0) {
|
|
7608
|
-
|
|
8346
|
+
process.stderr.write("No conversations found.\n");
|
|
7609
8347
|
process.exit(0);
|
|
7610
8348
|
}
|
|
7611
8349
|
return promptSelect(
|
|
@@ -7618,17 +8356,17 @@ async function pickConversation(arbi, workspaceId, message = "Select conversatio
|
|
|
7618
8356
|
);
|
|
7619
8357
|
}
|
|
7620
8358
|
function registerConversationsCommand(program2) {
|
|
7621
|
-
const conv = program2.command("
|
|
8359
|
+
const conv = program2.command("convo").description("Conversations: list, switch, threads, delete, share, title, message, trace");
|
|
7622
8360
|
conv.command("list").description("List conversations in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output as JSON").action(
|
|
7623
8361
|
(opts) => runAction(async () => {
|
|
7624
8362
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
7625
8363
|
const data = await sdk.conversations.listConversations(arbi);
|
|
7626
8364
|
if (opts.json) {
|
|
7627
|
-
|
|
8365
|
+
printJson(data);
|
|
7628
8366
|
return;
|
|
7629
8367
|
}
|
|
7630
8368
|
if (data.length === 0) {
|
|
7631
|
-
|
|
8369
|
+
process.stderr.write("No conversations found.\n");
|
|
7632
8370
|
return;
|
|
7633
8371
|
}
|
|
7634
8372
|
printTable(
|
|
@@ -7650,8 +8388,39 @@ function registerConversationsCommand(program2) {
|
|
|
7650
8388
|
console.log(JSON.stringify(data, null, 2));
|
|
7651
8389
|
})()
|
|
7652
8390
|
);
|
|
7653
|
-
conv.command("
|
|
8391
|
+
conv.command("switch <conversation-id>").description(
|
|
8392
|
+
"Continue a previous conversation \u2014 next `arbi ask` resumes from its latest message"
|
|
8393
|
+
).action(
|
|
7654
8394
|
(conversationId) => runAction(async () => {
|
|
8395
|
+
const { arbi, workspaceId } = await resolveWorkspace();
|
|
8396
|
+
const data = await sdk.conversations.getConversationThreads(arbi, conversationId);
|
|
8397
|
+
const threads = data.threads ?? [];
|
|
8398
|
+
if (threads.length === 0) {
|
|
8399
|
+
process.stderr.write(`Conversation ${conversationId} has no threads.
|
|
8400
|
+
`);
|
|
8401
|
+
process.exit(3);
|
|
8402
|
+
}
|
|
8403
|
+
const primary = threads[0];
|
|
8404
|
+
const leafId = primary.leaf_message_ext_id;
|
|
8405
|
+
const lastAssistant = [...primary.history ?? []].reverse().find((m) => m.role === "assistant");
|
|
8406
|
+
const resumeId = leafId ?? lastAssistant?.external_id;
|
|
8407
|
+
if (!resumeId) {
|
|
8408
|
+
process.stderr.write(
|
|
8409
|
+
`No resumable message in conversation ${conversationId}. (Empty thread or never reached an assistant turn.)
|
|
8410
|
+
`
|
|
8411
|
+
);
|
|
8412
|
+
process.exit(3);
|
|
8413
|
+
}
|
|
8414
|
+
updateChatSession({
|
|
8415
|
+
conversationExtId: conversationId,
|
|
8416
|
+
lastMessageExtId: resumeId,
|
|
8417
|
+
workspaceId
|
|
8418
|
+
});
|
|
8419
|
+
success(`Switched to conversation ${conversationId} (resume from ${resumeId}).`);
|
|
8420
|
+
})()
|
|
8421
|
+
);
|
|
8422
|
+
conv.command("delete [conversation-id]").description("Delete a conversation (interactive picker if no ID given)").option("--dry-run", "Preview which conversation would be deleted (no SDK call)").action(
|
|
8423
|
+
(conversationId, opts) => runAction(async () => {
|
|
7655
8424
|
const { arbi, workspaceId } = await resolveWorkspace();
|
|
7656
8425
|
if (!conversationId)
|
|
7657
8426
|
conversationId = await pickConversation(
|
|
@@ -7659,6 +8428,10 @@ function registerConversationsCommand(program2) {
|
|
|
7659
8428
|
workspaceId,
|
|
7660
8429
|
"Select conversation to delete"
|
|
7661
8430
|
);
|
|
8431
|
+
if (opts?.dryRun) {
|
|
8432
|
+
dryRun("delete conversation", conversationId);
|
|
8433
|
+
return;
|
|
8434
|
+
}
|
|
7662
8435
|
const data = await sdk.conversations.deleteConversation(arbi, conversationId);
|
|
7663
8436
|
success(data?.detail ?? `Deleted conversation ${conversationId}`);
|
|
7664
8437
|
})()
|
|
@@ -7681,11 +8454,18 @@ function registerConversationsCommand(program2) {
|
|
|
7681
8454
|
success(data?.detail ?? `Updated title to: ${title}`);
|
|
7682
8455
|
})()
|
|
7683
8456
|
);
|
|
7684
|
-
conv.command("message <message-id>").description("Get message details").action(
|
|
8457
|
+
conv.command("message <message-id>").description("Get message details (full decrypted JSON, useful for debugging)").action(
|
|
7685
8458
|
(messageId) => runAction(async () => {
|
|
7686
8459
|
const { arbi } = await resolveWorkspace();
|
|
7687
8460
|
const data = await sdk.conversations.getMessage(arbi, messageId);
|
|
7688
|
-
|
|
8461
|
+
printJson(data);
|
|
8462
|
+
})()
|
|
8463
|
+
);
|
|
8464
|
+
conv.command("trace <message-id>").description("Decrypted execution trace for one agent response (alias for `message`)").action(
|
|
8465
|
+
(messageId) => runAction(async () => {
|
|
8466
|
+
const { arbi } = await resolveWorkspace();
|
|
8467
|
+
const data = await sdk.conversations.getMessage(arbi, messageId);
|
|
8468
|
+
printJson(data);
|
|
7689
8469
|
})()
|
|
7690
8470
|
);
|
|
7691
8471
|
conv.command("delete-message <message-id>").description("Delete a message and its descendants").action(
|
|
@@ -7695,20 +8475,18 @@ function registerConversationsCommand(program2) {
|
|
|
7695
8475
|
success(data?.detail ?? `Deleted message ${messageId}`);
|
|
7696
8476
|
})()
|
|
7697
8477
|
);
|
|
7698
|
-
conv.
|
|
7699
|
-
const
|
|
7700
|
-
|
|
7701
|
-
if (opts.workspace) args.push("-w", opts.workspace);
|
|
7702
|
-
await cmd.commands.find((c) => c.name() === "list").parseAsync(args, { from: "user" });
|
|
8478
|
+
conv.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
8479
|
+
const tail = conv.args ?? [];
|
|
8480
|
+
await conv.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
7703
8481
|
});
|
|
7704
8482
|
}
|
|
7705
8483
|
function registerSettingsCommand(program2) {
|
|
7706
|
-
const settings = program2.command("settings").description("
|
|
8484
|
+
const settings = program2.command("settings").description("User settings: get, set");
|
|
7707
8485
|
settings.command("get").description("Show current user settings (always JSON; --json accepted for consistency)").option("--json", "Output as JSON (default \u2014 always JSON)").action(
|
|
7708
8486
|
() => runAction(async () => {
|
|
7709
8487
|
const { arbi } = await resolveAuth();
|
|
7710
8488
|
const data = await sdk.settings.getSettings(arbi);
|
|
7711
|
-
|
|
8489
|
+
printJson(data);
|
|
7712
8490
|
})()
|
|
7713
8491
|
);
|
|
7714
8492
|
settings.command("set <json>").description("Update user settings (pass JSON object)").action(
|
|
@@ -7723,6 +8501,7 @@ function registerSettingsCommand(program2) {
|
|
|
7723
8501
|
await settings.commands.find((c) => c.name() === "get").parseAsync([], { from: "user" });
|
|
7724
8502
|
});
|
|
7725
8503
|
}
|
|
8504
|
+
init_prompts();
|
|
7726
8505
|
var MODEL_SECTIONS = [
|
|
7727
8506
|
"Agents",
|
|
7728
8507
|
"QueryLLM",
|
|
@@ -7866,7 +8645,7 @@ async function buildConfigInteractively(arbi) {
|
|
|
7866
8645
|
return body;
|
|
7867
8646
|
}
|
|
7868
8647
|
function registerAgentconfigCommand(program2) {
|
|
7869
|
-
const ac = program2.command("agentconfig").description("
|
|
8648
|
+
const ac = program2.command("agentconfig").description("Agent configurations: list, get, save, delete, schema");
|
|
7870
8649
|
ac.command("list").description("List saved configuration versions").action(
|
|
7871
8650
|
runAction(async () => {
|
|
7872
8651
|
const { arbi } = await resolveAuth();
|
|
@@ -8013,12 +8792,29 @@ function registerTuiCommand(program2) {
|
|
|
8013
8792
|
});
|
|
8014
8793
|
}
|
|
8015
8794
|
function registerUpdateCommand(program2) {
|
|
8016
|
-
const update = program2.command("update").description("Update ARBI CLI to the latest version").action(() => {
|
|
8795
|
+
const update = program2.command("update").description("Update ARBI CLI to the latest version (use --check to only probe)").option("--check", "Only report whether an update is available \u2014 never install anything").option("--json", "Output {current, latest, update_available} as JSON (implies --check)").action((opts) => {
|
|
8017
8796
|
const current = getCurrentVersion();
|
|
8018
|
-
|
|
8019
|
-
|
|
8797
|
+
const checkOnly = Boolean(opts.check || opts.json);
|
|
8798
|
+
if (!checkOnly) {
|
|
8799
|
+
label("Current version:", current);
|
|
8800
|
+
console.log("Checking for updates...\n");
|
|
8801
|
+
}
|
|
8020
8802
|
try {
|
|
8021
8803
|
const latest = getLatestVersion(true) || child_process.execSync("npm view @arbidocs/cli version", { encoding: "utf8" }).trim();
|
|
8804
|
+
if (checkOnly) {
|
|
8805
|
+
const updateAvailable = latest !== current;
|
|
8806
|
+
if (opts.json) {
|
|
8807
|
+
printJson({ current, latest, update_available: updateAvailable });
|
|
8808
|
+
} else if (updateAvailable) {
|
|
8809
|
+
label("Current version:", current);
|
|
8810
|
+
label("Latest version:", latest);
|
|
8811
|
+
console.log("Update available. Run `arbi update` to install.");
|
|
8812
|
+
} else {
|
|
8813
|
+
label("Current version:", current);
|
|
8814
|
+
success("Already up to date.");
|
|
8815
|
+
}
|
|
8816
|
+
process.exit(updateAvailable ? 1 : 0);
|
|
8817
|
+
}
|
|
8022
8818
|
if (latest === current) {
|
|
8023
8819
|
success("Already up to date.");
|
|
8024
8820
|
return;
|
|
@@ -8031,6 +8827,10 @@ function registerUpdateCommand(program2) {
|
|
|
8031
8827
|
Updated to ${latest}.`);
|
|
8032
8828
|
} catch (err) {
|
|
8033
8829
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8830
|
+
if (opts.json) {
|
|
8831
|
+
printJson({ current, latest: null, update_available: null, error: msg });
|
|
8832
|
+
process.exit(1);
|
|
8833
|
+
}
|
|
8034
8834
|
error(`Update failed: ${msg}`);
|
|
8035
8835
|
error("\nYou can update manually with:");
|
|
8036
8836
|
dim(" npm install -g @arbidocs/cli@latest");
|
|
@@ -8042,6 +8842,7 @@ Updated to ${latest}.`);
|
|
|
8042
8842
|
success("Auto-update enabled. ARBI CLI will update automatically on login.");
|
|
8043
8843
|
});
|
|
8044
8844
|
}
|
|
8845
|
+
init_prompts();
|
|
8045
8846
|
function isExitPromptError(err) {
|
|
8046
8847
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
8047
8848
|
}
|
|
@@ -8215,7 +9016,9 @@ async function getVerificationCode2(email, apiKey) {
|
|
|
8215
9016
|
return Array.isArray(words) ? words.join(" ") : String(words);
|
|
8216
9017
|
}
|
|
8217
9018
|
function registerAgentCreateCommand(program2) {
|
|
8218
|
-
program2.command("agent-create").description(
|
|
9019
|
+
program2.command("agent-create").description(
|
|
9020
|
+
"Create a standalone bot/test ACCOUNT via SUPPORT_API_KEY (different from `arbi agent create`, which makes a child agent under the current user)"
|
|
9021
|
+
).argument("[url]", "Server URL (auto-detected if omitted)").option("-p, --password <password>", "Account password", DEFAULT_PASSWORD).option("--workspace-name <name>", "Workspace name", "Agent Workspace").option("--email <email>", "Custom email (default: agent-{timestamp}@{domain})").action(
|
|
8219
9022
|
async (url, opts) => {
|
|
8220
9023
|
const { config, source } = store.resolveConfigWithFallbacks();
|
|
8221
9024
|
if (url) {
|
|
@@ -8306,6 +9109,7 @@ function registerAgentCreateCommand(program2) {
|
|
|
8306
9109
|
}
|
|
8307
9110
|
);
|
|
8308
9111
|
}
|
|
9112
|
+
init_prompts();
|
|
8309
9113
|
function generatePassword() {
|
|
8310
9114
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
8311
9115
|
const bytes = new Uint8Array(20);
|
|
@@ -8313,8 +9117,12 @@ function generatePassword() {
|
|
|
8313
9117
|
return Array.from(bytes, (b) => chars[b % chars.length]).join("");
|
|
8314
9118
|
}
|
|
8315
9119
|
function registerAgentCommand(program2) {
|
|
8316
|
-
const agent = program2.command("agent").description(
|
|
8317
|
-
|
|
9120
|
+
const agent = program2.command("agent").description(
|
|
9121
|
+
"Child agents under your user: list, create, delete (for SUPPORT_API_KEY bot accounts, use `arbi agent-create`)"
|
|
9122
|
+
);
|
|
9123
|
+
agent.command("create").description(
|
|
9124
|
+
"Create a child persistent agent owned by the current user (different from `arbi agent-create`, which is a SUPPORT_API_KEY bot-account flow)"
|
|
9125
|
+
).option("--name <name>", "Agent name (letters, digits, hyphens, underscores)").option("-w, --workspace <ids...>", "Workspace IDs to share with the agent").option("--role <role>", "Workspace role: collaborator or guest", "collaborator").action(
|
|
8318
9126
|
(opts) => runAction(async () => {
|
|
8319
9127
|
const { arbi, loginResult } = await resolveAuth();
|
|
8320
9128
|
const config = resolveConfig();
|
|
@@ -8390,12 +9198,16 @@ function registerAgentCommand(program2) {
|
|
|
8390
9198
|
dim(`On the agent machine: arbi login --email ${agent2.email} --password <password>`);
|
|
8391
9199
|
})()
|
|
8392
9200
|
);
|
|
8393
|
-
agent.command("list").description("List your persistent agents").action(
|
|
8394
|
-
runAction(async () => {
|
|
9201
|
+
agent.command("list").description("List your persistent agents").option("--json", "Output as JSON (recommended for scripting / agents)").action(
|
|
9202
|
+
(opts) => runAction(async () => {
|
|
8395
9203
|
const { arbi } = await resolveAuth();
|
|
8396
9204
|
const data = await sdk.agents.listAgents(arbi);
|
|
9205
|
+
if (opts?.json) {
|
|
9206
|
+
printJson(data);
|
|
9207
|
+
return;
|
|
9208
|
+
}
|
|
8397
9209
|
if (data.length === 0) {
|
|
8398
|
-
|
|
9210
|
+
process.stderr.write("No agents found.\n");
|
|
8399
9211
|
return;
|
|
8400
9212
|
}
|
|
8401
9213
|
printTable(
|
|
@@ -8410,17 +9222,18 @@ function registerAgentCommand(program2) {
|
|
|
8410
9222
|
],
|
|
8411
9223
|
data
|
|
8412
9224
|
);
|
|
8413
|
-
})
|
|
9225
|
+
})()
|
|
8414
9226
|
);
|
|
8415
|
-
agent.command("delete [agent-id]").description("Delete a persistent agent (picker if no ID)").action(
|
|
8416
|
-
(agentId) => runAction(async () => {
|
|
9227
|
+
agent.command("delete [agent-id]").description("Delete a persistent agent (picker if no ID)").option("-y, --yes", "Skip confirmation (required in non-TTY shells)").option("--dry-run", "Preview which agent would be deleted (no SDK call)").action(
|
|
9228
|
+
(agentId, opts) => runAction(async () => {
|
|
8417
9229
|
const { arbi } = await resolveAuth();
|
|
8418
9230
|
if (!agentId) {
|
|
8419
9231
|
const data = await sdk.agents.listAgents(arbi);
|
|
8420
9232
|
if (data.length === 0) {
|
|
8421
|
-
|
|
9233
|
+
process.stderr.write("No agents to delete.\n");
|
|
8422
9234
|
return;
|
|
8423
9235
|
}
|
|
9236
|
+
requireInteractive("Pass <agent-id> directly: arbi agent delete <id>");
|
|
8424
9237
|
agentId = await promptSelect(
|
|
8425
9238
|
"Select agent to delete",
|
|
8426
9239
|
data.map((a) => ({
|
|
@@ -8429,16 +9242,30 @@ function registerAgentCommand(program2) {
|
|
|
8429
9242
|
}))
|
|
8430
9243
|
);
|
|
8431
9244
|
}
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
console.log("Cancelled.");
|
|
9245
|
+
if (opts?.dryRun) {
|
|
9246
|
+
dryRun("delete agent", agentId);
|
|
8435
9247
|
return;
|
|
8436
9248
|
}
|
|
9249
|
+
if (!opts?.yes) {
|
|
9250
|
+
requireInteractive("Pass -y/--yes to confirm in non-TTY shells.");
|
|
9251
|
+
const confirmed = await promptConfirm(`Delete agent ${agentId}?`, false);
|
|
9252
|
+
if (!confirmed) {
|
|
9253
|
+
console.log("Cancelled.");
|
|
9254
|
+
return;
|
|
9255
|
+
}
|
|
9256
|
+
}
|
|
8437
9257
|
await sdk.agents.deleteAgents(arbi, [agentId]);
|
|
8438
9258
|
success(`Agent ${agentId} deleted.`);
|
|
8439
9259
|
})()
|
|
8440
9260
|
);
|
|
8441
|
-
agent.action(async () => {
|
|
9261
|
+
agent.arguments("[maybeSubcommand]").action(async (maybe) => {
|
|
9262
|
+
if (maybe) {
|
|
9263
|
+
suggestSubcommandAndExit(
|
|
9264
|
+
"agent",
|
|
9265
|
+
maybe,
|
|
9266
|
+
agent.commands.map((c) => c.name())
|
|
9267
|
+
);
|
|
9268
|
+
}
|
|
8442
9269
|
await agent.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
|
|
8443
9270
|
});
|
|
8444
9271
|
}
|
|
@@ -8465,15 +9292,33 @@ function statusColor2(s) {
|
|
|
8465
9292
|
return s;
|
|
8466
9293
|
}
|
|
8467
9294
|
function registerTaskCommand(program2) {
|
|
8468
|
-
const task = program2.command("task").description("
|
|
8469
|
-
task.action(async (
|
|
8470
|
-
|
|
9295
|
+
const task = program2.command("task").description("Background tasks (from `arbi ask -b`): list, status, result");
|
|
9296
|
+
task.arguments("[maybeSubcommand]").action(async (maybe) => {
|
|
9297
|
+
if (maybe) {
|
|
9298
|
+
suggestSubcommandAndExit(
|
|
9299
|
+
"task",
|
|
9300
|
+
maybe,
|
|
9301
|
+
task.commands.map((c) => c.name())
|
|
9302
|
+
);
|
|
9303
|
+
}
|
|
9304
|
+
await task.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
|
|
8471
9305
|
});
|
|
8472
|
-
task.command("list").description("List background tasks").action(
|
|
8473
|
-
() => runAction(async () => {
|
|
9306
|
+
task.command("list").description("List background tasks").option("--json", "Output as JSON (recommended for scripting / agents)").action(
|
|
9307
|
+
(opts) => runAction(async () => {
|
|
8474
9308
|
const tasks = getTasks();
|
|
9309
|
+
if (opts?.json) {
|
|
9310
|
+
const out = tasks.map((t) => ({
|
|
9311
|
+
id: t.id,
|
|
9312
|
+
status: t.status,
|
|
9313
|
+
question: t.question,
|
|
9314
|
+
age_seconds: Math.round((Date.now() - new Date(t.submittedAt).getTime()) / 1e3),
|
|
9315
|
+
submitted_at: t.submittedAt
|
|
9316
|
+
}));
|
|
9317
|
+
printJson(out);
|
|
9318
|
+
return;
|
|
9319
|
+
}
|
|
8475
9320
|
if (tasks.length === 0) {
|
|
8476
|
-
|
|
9321
|
+
process.stderr.write('No tasks. Submit one with: arbi ask -b "your question"\n');
|
|
8477
9322
|
return;
|
|
8478
9323
|
}
|
|
8479
9324
|
printTable(
|
|
@@ -8556,8 +9401,8 @@ compdef _arbi_completions arbi
|
|
|
8556
9401
|
${MARKER_END}`;
|
|
8557
9402
|
function getShellRcPath2() {
|
|
8558
9403
|
const shell = process.env.SHELL || "";
|
|
8559
|
-
if (shell.includes("zsh")) return path5.join(
|
|
8560
|
-
return path5.join(
|
|
9404
|
+
if (shell.includes("zsh")) return path5.join(os2.homedir(), ".zshrc");
|
|
9405
|
+
return path5.join(os2.homedir(), ".bashrc");
|
|
8561
9406
|
}
|
|
8562
9407
|
function isZsh() {
|
|
8563
9408
|
return (process.env.SHELL || "").includes("zsh");
|
|
@@ -8576,7 +9421,7 @@ function removeCompletionBlock(content) {
|
|
|
8576
9421
|
return before + after;
|
|
8577
9422
|
}
|
|
8578
9423
|
function registerCompletionCommand(program2) {
|
|
8579
|
-
const completion = program2.command("completion").description("
|
|
9424
|
+
const completion = program2.command("completion").description("Shell tab completion: install, uninstall, refresh");
|
|
8580
9425
|
completion.command("install").description("Install shell tab completion (auto-detects bash or zsh \u2014 no shell arg needed)").action(() => {
|
|
8581
9426
|
const rcPath = getShellRcPath2();
|
|
8582
9427
|
if (isCompletionInstalled(rcPath)) {
|
|
@@ -8614,7 +9459,7 @@ function registerCompletionCommand(program2) {
|
|
|
8614
9459
|
}
|
|
8615
9460
|
function resolvePath(p) {
|
|
8616
9461
|
const raw = p ?? ".";
|
|
8617
|
-
const expanded = raw.startsWith("~") ? raw.replace("~",
|
|
9462
|
+
const expanded = raw.startsWith("~") ? raw.replace("~", os2.homedir()) : raw;
|
|
8618
9463
|
return path5.resolve(expanded);
|
|
8619
9464
|
}
|
|
8620
9465
|
function formatSize2(bytes) {
|
|
@@ -8623,9 +9468,20 @@ function formatSize2(bytes) {
|
|
|
8623
9468
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
8624
9469
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
|
|
8625
9470
|
}
|
|
9471
|
+
function entryToRow(dir, name) {
|
|
9472
|
+
try {
|
|
9473
|
+
const stat = fs5.statSync(path5.join(dir, name));
|
|
9474
|
+
if (stat.isDirectory()) return { name, type: "dir", size: null };
|
|
9475
|
+
if (stat.isSymbolicLink()) return { name, type: "symlink", size: stat.size };
|
|
9476
|
+
if (stat.isFile()) return { name, type: "file", size: stat.size };
|
|
9477
|
+
return { name, type: "other", size: stat.size };
|
|
9478
|
+
} catch {
|
|
9479
|
+
return { name, type: "other", size: null };
|
|
9480
|
+
}
|
|
9481
|
+
}
|
|
8626
9482
|
function registerLocalCommand(program2) {
|
|
8627
9483
|
const local = program2.command("local").description("Local filesystem operations");
|
|
8628
|
-
local.command("ls [path]").description("List files in a directory").option("-l, --long", "Show file sizes and types").action((path7, opts) => {
|
|
9484
|
+
local.command("ls [path]").description("List files in a directory").option("-l, --long", "Show file sizes and types (ignored when --json is set)").option("-a, --all", "Include dotfiles").option("--json", "Output as JSON array of {name, type, size}").action((path7, opts) => {
|
|
8629
9485
|
const dir = resolvePath(path7);
|
|
8630
9486
|
let entries;
|
|
8631
9487
|
try {
|
|
@@ -8634,33 +9490,46 @@ function registerLocalCommand(program2) {
|
|
|
8634
9490
|
console.error(`Cannot read directory: ${dir}`);
|
|
8635
9491
|
process.exit(1);
|
|
8636
9492
|
}
|
|
8637
|
-
|
|
9493
|
+
const sorted = entries.filter((e) => opts?.all || !e.startsWith(".")).sort();
|
|
9494
|
+
if (opts?.json) {
|
|
9495
|
+
const rows = sorted.map((name) => entryToRow(dir, name));
|
|
9496
|
+
printJson(rows);
|
|
9497
|
+
return;
|
|
9498
|
+
}
|
|
9499
|
+
for (const entry of sorted) {
|
|
8638
9500
|
if (opts?.long) {
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
const size = stat.isFile() ? formatSize2(stat.size) : "-";
|
|
8643
|
-
console.log(`${type.padEnd(5)} ${size.padStart(10)} ${entry}`);
|
|
8644
|
-
} catch {
|
|
8645
|
-
console.log(`? ${"-".padStart(10)} ${entry}`);
|
|
8646
|
-
}
|
|
9501
|
+
const row = entryToRow(dir, entry);
|
|
9502
|
+
const size = row.size !== null && row.type === "file" ? formatSize2(row.size) : "-";
|
|
9503
|
+
console.log(`${row.type.padEnd(5)} ${size.padStart(10)} ${entry}`);
|
|
8647
9504
|
} else {
|
|
8648
9505
|
console.log(entry);
|
|
8649
9506
|
}
|
|
8650
9507
|
}
|
|
8651
9508
|
});
|
|
8652
|
-
local.command("find <pattern>").description('Find files matching a glob (e.g. "**/*.pdf")').option("-d, --dir <path>", "Directory to search in", ".").action((pattern, opts) => {
|
|
9509
|
+
local.command("find <pattern>").description('Find files matching a glob (e.g. "**/*.pdf")').option("-d, --dir <path>", "Directory to search in", ".").option("--json", "Output as JSON array of matched paths").action((pattern, opts) => {
|
|
8653
9510
|
const dir = resolvePath(opts.dir);
|
|
8654
9511
|
const results = fs5.globSync(pattern, { cwd: dir });
|
|
9512
|
+
if (opts.json) {
|
|
9513
|
+
if (results.length === 0) {
|
|
9514
|
+
process.stderr.write(`No files matching "${pattern}" in ${dir}
|
|
9515
|
+
`);
|
|
9516
|
+
}
|
|
9517
|
+
printJson(results);
|
|
9518
|
+
return;
|
|
9519
|
+
}
|
|
8655
9520
|
if (results.length === 0) {
|
|
8656
|
-
|
|
9521
|
+
process.stderr.write(`No files matching "${pattern}" in ${dir}
|
|
9522
|
+
`);
|
|
8657
9523
|
return;
|
|
8658
9524
|
}
|
|
8659
9525
|
for (const file of results) {
|
|
8660
9526
|
console.log(file);
|
|
8661
9527
|
}
|
|
8662
9528
|
});
|
|
8663
|
-
local.command("cat <file>").description("Print file contents").option("--head <lines>", "Only show first N lines").
|
|
9529
|
+
local.command("cat <file>").description("Print file contents").option("--head <lines>", "Only show first N lines").option(
|
|
9530
|
+
"--json",
|
|
9531
|
+
"Output as {path, bytes, content} \u2014 useful when content has newlines an agent needs to handle"
|
|
9532
|
+
).action((file, opts) => {
|
|
8664
9533
|
const filePath = resolvePath(file);
|
|
8665
9534
|
let content;
|
|
8666
9535
|
try {
|
|
@@ -8671,18 +9540,51 @@ function registerLocalCommand(program2) {
|
|
|
8671
9540
|
}
|
|
8672
9541
|
if (opts.head) {
|
|
8673
9542
|
const n = parseInt(opts.head, 10);
|
|
8674
|
-
|
|
8675
|
-
} else {
|
|
8676
|
-
console.log(content);
|
|
9543
|
+
content = content.split("\n").slice(0, n).join("\n");
|
|
8677
9544
|
}
|
|
9545
|
+
if (opts.json) {
|
|
9546
|
+
printJson({ path: filePath, bytes: Buffer.byteLength(content, "utf-8"), content });
|
|
9547
|
+
return;
|
|
9548
|
+
}
|
|
9549
|
+
console.log(content);
|
|
8678
9550
|
});
|
|
8679
|
-
local.command("tree [path]").description("Show directory tree").option("-d, --depth <n>", "Maximum depth", "3").action((path7, opts) => {
|
|
9551
|
+
local.command("tree [path]").description("Show directory tree").option("-d, --depth <n>", "Maximum depth", "3").option("--json", "Output as nested {name, type, children} tree").action((path7, opts) => {
|
|
8680
9552
|
const dir = resolvePath(path7);
|
|
8681
9553
|
const maxDepth = parseInt(opts?.depth ?? "3", 10);
|
|
9554
|
+
if (opts?.json) {
|
|
9555
|
+
printJson(buildTree(dir, maxDepth, 0));
|
|
9556
|
+
return;
|
|
9557
|
+
}
|
|
8682
9558
|
console.log(path5.basename(dir) + "/");
|
|
8683
9559
|
printTree(dir, "", maxDepth, 0);
|
|
8684
9560
|
});
|
|
8685
9561
|
}
|
|
9562
|
+
function buildTree(dir, maxDepth, depth) {
|
|
9563
|
+
const node = { name: path5.basename(dir), type: "dir", children: [] };
|
|
9564
|
+
if (depth >= maxDepth) return node;
|
|
9565
|
+
let entries;
|
|
9566
|
+
try {
|
|
9567
|
+
entries = fs5.readdirSync(dir).filter((e) => !e.startsWith(".")).sort();
|
|
9568
|
+
} catch {
|
|
9569
|
+
return node;
|
|
9570
|
+
}
|
|
9571
|
+
for (const entry of entries) {
|
|
9572
|
+
const full = path5.join(dir, entry);
|
|
9573
|
+
let isDir = false;
|
|
9574
|
+
try {
|
|
9575
|
+
isDir = fs5.statSync(full).isDirectory();
|
|
9576
|
+
} catch {
|
|
9577
|
+
node.children.push({ name: entry, type: "other" });
|
|
9578
|
+
continue;
|
|
9579
|
+
}
|
|
9580
|
+
if (isDir) {
|
|
9581
|
+
node.children.push(buildTree(full, maxDepth, depth + 1));
|
|
9582
|
+
} else {
|
|
9583
|
+
node.children.push({ name: entry, type: "file" });
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
9586
|
+
return node;
|
|
9587
|
+
}
|
|
8686
9588
|
function printTree(dir, prefix, maxDepth, depth) {
|
|
8687
9589
|
if (depth >= maxDepth) return;
|
|
8688
9590
|
let entries;
|
|
@@ -8709,14 +9611,25 @@ function printTree(dir, prefix, maxDepth, depth) {
|
|
|
8709
9611
|
}
|
|
8710
9612
|
}
|
|
8711
9613
|
}
|
|
9614
|
+
function humanizeTtl(seconds) {
|
|
9615
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "";
|
|
9616
|
+
if (seconds < 60) return `${seconds}s`;
|
|
9617
|
+
if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
|
|
9618
|
+
if (seconds < 86400) return `${Math.round(seconds / 3600)}h`;
|
|
9619
|
+
return `${Math.round(seconds / 86400)}d`;
|
|
9620
|
+
}
|
|
8712
9621
|
function registerSessionCommand(program2) {
|
|
8713
9622
|
const session = program2.command("session").description("List and manage sessions");
|
|
8714
|
-
session.command("list").description("List active sessions").action(
|
|
8715
|
-
runAction(async () => {
|
|
9623
|
+
session.command("list").description("List active sessions").option("--json", "Output as JSON (recommended for scripting / agents)").action(
|
|
9624
|
+
(opts) => runAction(async () => {
|
|
8716
9625
|
const { arbi } = await resolveAuth();
|
|
8717
9626
|
const data = await sdk.sessions.listSessions(arbi);
|
|
9627
|
+
if (opts?.json) {
|
|
9628
|
+
printJson(data);
|
|
9629
|
+
return;
|
|
9630
|
+
}
|
|
8718
9631
|
if (data.length === 0) {
|
|
8719
|
-
|
|
9632
|
+
process.stderr.write("No active sessions.\n");
|
|
8720
9633
|
return;
|
|
8721
9634
|
}
|
|
8722
9635
|
printTable(
|
|
@@ -8728,7 +9641,13 @@ function registerSessionCommand(program2) {
|
|
|
8728
9641
|
},
|
|
8729
9642
|
{ header: "NAME", width: 20, value: (r) => r.name || "" },
|
|
8730
9643
|
{ header: "STATUS", width: 12, value: (r) => r.status },
|
|
8731
|
-
{
|
|
9644
|
+
{
|
|
9645
|
+
header: "TTL",
|
|
9646
|
+
width: 10,
|
|
9647
|
+
// Raw `<n>s` was unreadable past a few hours; humanize once the
|
|
9648
|
+
// window grows beyond a minute.
|
|
9649
|
+
value: (r) => humanizeTtl(r.ttl)
|
|
9650
|
+
},
|
|
8732
9651
|
{
|
|
8733
9652
|
header: "WORKSPACES",
|
|
8734
9653
|
width: 12,
|
|
@@ -8737,12 +9656,423 @@ function registerSessionCommand(program2) {
|
|
|
8737
9656
|
],
|
|
8738
9657
|
data
|
|
8739
9658
|
);
|
|
8740
|
-
})
|
|
9659
|
+
})()
|
|
8741
9660
|
);
|
|
8742
|
-
session.action(async () => {
|
|
9661
|
+
session.arguments("[maybeSubcommand]").action(async (maybe) => {
|
|
9662
|
+
if (maybe) {
|
|
9663
|
+
suggestSubcommandAndExit(
|
|
9664
|
+
"session",
|
|
9665
|
+
maybe,
|
|
9666
|
+
session.commands.map((c) => c.name())
|
|
9667
|
+
);
|
|
9668
|
+
}
|
|
8743
9669
|
await session.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
|
|
8744
9670
|
});
|
|
8745
9671
|
}
|
|
9672
|
+
function registerNotificationsCommand(program2) {
|
|
9673
|
+
const notif = program2.command("notifications").enablePositionalOptions().description("Non-DM notifications");
|
|
9674
|
+
notif.command("list").description("List non-DM notifications (workspace invites, contact accepts, \u2026)").option("--unread", "Only show unread notifications").option("--type <comma-list>", "Filter to specific type(s), e.g. workspaceuser_invited").option("-l, --limit <n>", "Cap to N most-recent", (v) => parseInt(v, 10)).option("--json", "Output as JSON (recommended for scripting / agents)").action(
|
|
9675
|
+
(opts) => runAction(async () => {
|
|
9676
|
+
const { arbi, crypto: crypto2 } = await resolveDmCrypto();
|
|
9677
|
+
const all = await sdk.dm.listDecryptedDMs(arbi, crypto2);
|
|
9678
|
+
const nonDm = all.filter((r) => r.type !== "user_message");
|
|
9679
|
+
const typeFilter = opts.type ? new Set(
|
|
9680
|
+
opts.type.split(",").map((t) => t.trim()).filter(Boolean)
|
|
9681
|
+
) : null;
|
|
9682
|
+
const filtered = nonDm.filter((r) => {
|
|
9683
|
+
if (opts.unread && r.read) return false;
|
|
9684
|
+
if (typeFilter && !typeFilter.has(r.type ?? "")) return false;
|
|
9685
|
+
return true;
|
|
9686
|
+
});
|
|
9687
|
+
const limited = opts.limit && opts.limit > 0 ? filtered.slice(0, opts.limit) : filtered;
|
|
9688
|
+
if (opts.json) {
|
|
9689
|
+
printJson(limited);
|
|
9690
|
+
return;
|
|
9691
|
+
}
|
|
9692
|
+
if (limited.length === 0) {
|
|
9693
|
+
process.stderr.write("No notifications.\n");
|
|
9694
|
+
return;
|
|
9695
|
+
}
|
|
9696
|
+
printTable(
|
|
9697
|
+
[
|
|
9698
|
+
{ header: "ID", width: 16, value: (r) => r.external_id },
|
|
9699
|
+
{ header: "TYPE", width: 24, value: (r) => r.type ?? "" },
|
|
9700
|
+
{ header: "READ", width: 6, value: (r) => r.read ? "yes" : "no" },
|
|
9701
|
+
{
|
|
9702
|
+
header: "CONTENT",
|
|
9703
|
+
width: 50,
|
|
9704
|
+
// Non-DM rows usually carry a JSON-encoded payload in
|
|
9705
|
+
// `content`. Show the first slice and let the JSON path
|
|
9706
|
+
// expose the full thing for agents that want to introspect.
|
|
9707
|
+
value: (r) => truncate(r.content ?? "", 49)
|
|
9708
|
+
}
|
|
9709
|
+
],
|
|
9710
|
+
limited
|
|
9711
|
+
);
|
|
9712
|
+
})()
|
|
9713
|
+
);
|
|
9714
|
+
notif.command("mark-read [ids...]").description("Mark notifications as read (use --all to drain the unread queue)").option("--all", "Mark every unread non-DM notification as read").action(
|
|
9715
|
+
(ids, opts) => runAction(async () => {
|
|
9716
|
+
const { arbi, crypto: crypto2 } = await resolveDmCrypto();
|
|
9717
|
+
let toMark = ids && ids.length > 0 ? ids : void 0;
|
|
9718
|
+
if (!toMark && opts.all) {
|
|
9719
|
+
const all = await sdk.dm.listDecryptedDMs(arbi, crypto2);
|
|
9720
|
+
toMark = all.filter((r) => r.type !== "user_message" && !r.read).map((r) => r.external_id);
|
|
9721
|
+
}
|
|
9722
|
+
if (!toMark || toMark.length === 0) {
|
|
9723
|
+
process.stderr.write("Nothing to mark read. (Pass IDs or --all.)\n");
|
|
9724
|
+
return;
|
|
9725
|
+
}
|
|
9726
|
+
const data = await sdk.dm.markRead(arbi, toMark);
|
|
9727
|
+
process.stdout.write(`Marked ${data.length} notification(s) as read.
|
|
9728
|
+
`);
|
|
9729
|
+
})()
|
|
9730
|
+
);
|
|
9731
|
+
notif.command("delete [ids...]").description("Delete notifications by ID (or use --all to clear non-DM noise)").option("--all", "Delete every non-DM notification (workspace invites, contact accepts, ...)").option("--dry-run", "Preview which notifications would be deleted (no SDK call)").action(
|
|
9732
|
+
(ids, opts) => runAction(async () => {
|
|
9733
|
+
const { arbi, crypto: crypto2 } = await resolveDmCrypto();
|
|
9734
|
+
let toDelete = ids && ids.length > 0 ? ids : void 0;
|
|
9735
|
+
if (!toDelete && opts.all) {
|
|
9736
|
+
const all = await sdk.dm.listDecryptedDMs(arbi, crypto2);
|
|
9737
|
+
toDelete = all.filter((r) => r.type !== "user_message").map((r) => r.external_id);
|
|
9738
|
+
}
|
|
9739
|
+
if (!toDelete || toDelete.length === 0) {
|
|
9740
|
+
process.stderr.write("Nothing to delete. (Pass IDs or --all.)\n");
|
|
9741
|
+
return;
|
|
9742
|
+
}
|
|
9743
|
+
if (opts?.dryRun) {
|
|
9744
|
+
dryRun(`delete ${toDelete.length} notification(s)`, toDelete);
|
|
9745
|
+
return;
|
|
9746
|
+
}
|
|
9747
|
+
await sdk.dm.deleteDMs(arbi, toDelete);
|
|
9748
|
+
process.stdout.write(`Deleted ${toDelete.length} notification(s).
|
|
9749
|
+
`);
|
|
9750
|
+
})()
|
|
9751
|
+
);
|
|
9752
|
+
notif.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
9753
|
+
const tail = notif.args ?? [];
|
|
9754
|
+
const subcommands = notif.commands.map((c) => c.name());
|
|
9755
|
+
const firstNonFlag = tail.find((a) => !a.startsWith("-"));
|
|
9756
|
+
if (firstNonFlag && !subcommands.includes(firstNonFlag)) {
|
|
9757
|
+
suggestSubcommandAndExit("notifications", firstNonFlag, subcommands);
|
|
9758
|
+
}
|
|
9759
|
+
await notif.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
9760
|
+
});
|
|
9761
|
+
}
|
|
9762
|
+
function registerProjectCommand(program2) {
|
|
9763
|
+
const project = program2.command("project").description("Project tenancy: list, create, refresh");
|
|
9764
|
+
project.command("list").description("List your projects").option("--json", "Output as JSON").action(
|
|
9765
|
+
(opts) => runAction(async () => {
|
|
9766
|
+
const { arbi } = await resolveAuth();
|
|
9767
|
+
const data = await sdk.projects.listProjects(arbi);
|
|
9768
|
+
if (!emitListOrTable(data, opts, "No projects found.")) return;
|
|
9769
|
+
printTable(
|
|
9770
|
+
[
|
|
9771
|
+
{ header: "ID", width: 18, value: (r) => r.external_id },
|
|
9772
|
+
{ header: "NAME", width: 30, value: (r) => r.name ?? "" },
|
|
9773
|
+
{ header: "PLAN", width: 14, value: (r) => r.plan ?? "" },
|
|
9774
|
+
{ header: "ROLE", width: 14, value: (r) => r.role ?? "" }
|
|
9775
|
+
],
|
|
9776
|
+
data
|
|
9777
|
+
);
|
|
9778
|
+
})()
|
|
9779
|
+
);
|
|
9780
|
+
project.command("create <name>").description("Create a new project").action(
|
|
9781
|
+
(name) => runAction(async () => {
|
|
9782
|
+
const { arbi } = await resolveAuth();
|
|
9783
|
+
const data = await sdk.projects.createProject(arbi, name);
|
|
9784
|
+
success(`Created project: ${data.name} (${ref(data.external_id)})`);
|
|
9785
|
+
})()
|
|
9786
|
+
);
|
|
9787
|
+
project.command("refresh <id>").description("Refresh a project (force plan/usage sync from billing)").action(
|
|
9788
|
+
(id) => runAction(async () => {
|
|
9789
|
+
const { arbi } = await resolveAuth();
|
|
9790
|
+
await sdk.projects.refreshProject(arbi, id);
|
|
9791
|
+
success(`Refreshed project: ${ref(id)}`);
|
|
9792
|
+
})()
|
|
9793
|
+
);
|
|
9794
|
+
project.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
9795
|
+
const tail = project.args ?? [];
|
|
9796
|
+
const subcommands = project.commands.map((c) => c.name());
|
|
9797
|
+
const firstNonFlag = tail.find((a) => !a.startsWith("-"));
|
|
9798
|
+
if (firstNonFlag && !subcommands.includes(firstNonFlag)) {
|
|
9799
|
+
suggestSubcommandAndExit("project", firstNonFlag, subcommands);
|
|
9800
|
+
}
|
|
9801
|
+
await project.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
9802
|
+
});
|
|
9803
|
+
}
|
|
9804
|
+
function formatBytes(n) {
|
|
9805
|
+
if (n < 1024) return `${n}B`;
|
|
9806
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}K`;
|
|
9807
|
+
if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)}M`;
|
|
9808
|
+
return `${(n / (1024 * 1024 * 1024)).toFixed(1)}G`;
|
|
9809
|
+
}
|
|
9810
|
+
function registerFilesCommand(program2) {
|
|
9811
|
+
const files = program2.command("files").description("Ephemeral files: list, upload, get, delete, fetch content");
|
|
9812
|
+
files.command("list").description("List ephemeral files attached to your account").option("--purpose <name>", "Filter by purpose (default: assistants)").option("-l, --limit <n>", "Cap to N entries", (v) => parseInt(v, 10)).option("--json", "Output as JSON").action(
|
|
9813
|
+
(opts) => runAction(async () => {
|
|
9814
|
+
const { arbi } = await resolveWorkspace();
|
|
9815
|
+
const data = await sdk.files.listFiles(arbi, {
|
|
9816
|
+
purpose: opts.purpose ?? null,
|
|
9817
|
+
limit: opts.limit
|
|
9818
|
+
});
|
|
9819
|
+
const items = data.data ?? [];
|
|
9820
|
+
if (opts.json) {
|
|
9821
|
+
printJson(data);
|
|
9822
|
+
return;
|
|
9823
|
+
}
|
|
9824
|
+
if (!emitListOrTable(items, opts, "No files found.")) return;
|
|
9825
|
+
printTable(
|
|
9826
|
+
[
|
|
9827
|
+
{ header: "ID", width: 30, value: (r) => r.id },
|
|
9828
|
+
{
|
|
9829
|
+
header: "NAME",
|
|
9830
|
+
width: 30,
|
|
9831
|
+
value: (r) => truncate(r.filename ?? "", 29)
|
|
9832
|
+
},
|
|
9833
|
+
{ header: "BYTES", width: 10, value: (r) => formatBytes(r.bytes ?? 0) },
|
|
9834
|
+
{ header: "PURPOSE", width: 14, value: (r) => r.purpose ?? "" }
|
|
9835
|
+
],
|
|
9836
|
+
items
|
|
9837
|
+
);
|
|
9838
|
+
})()
|
|
9839
|
+
);
|
|
9840
|
+
files.command("upload <path>").description("Upload a file (file ID is returned for use with arbi ask -b --attach)").option("--purpose <name>", "OpenAI purpose tag (default: assistants)", "assistants").action(
|
|
9841
|
+
(path7, opts) => runAction(async () => {
|
|
9842
|
+
const { arbi, loginResult } = await resolveWorkspace();
|
|
9843
|
+
const filePath = path5.resolve(path7);
|
|
9844
|
+
let bytes;
|
|
9845
|
+
try {
|
|
9846
|
+
bytes = fs5.readFileSync(filePath);
|
|
9847
|
+
} catch {
|
|
9848
|
+
error(`Cannot read file: ${filePath}`);
|
|
9849
|
+
process.exit(3);
|
|
9850
|
+
}
|
|
9851
|
+
const blob = new Blob([new Uint8Array(bytes)]);
|
|
9852
|
+
const data = await sdk.files.uploadFile(
|
|
9853
|
+
{
|
|
9854
|
+
baseUrl: arbi.fetch.baseUrl,
|
|
9855
|
+
accessToken: loginResult.accessToken
|
|
9856
|
+
},
|
|
9857
|
+
blob,
|
|
9858
|
+
path5.basename(filePath),
|
|
9859
|
+
opts.purpose ?? "assistants"
|
|
9860
|
+
);
|
|
9861
|
+
success(`Uploaded: ${ref(data.id)} (${formatBytes(data.bytes ?? 0)})`);
|
|
9862
|
+
})()
|
|
9863
|
+
);
|
|
9864
|
+
files.command("get <id>").description("Show a file's metadata (always JSON \u2014 the metadata IS the data)").action(
|
|
9865
|
+
(id) => runAction(async () => {
|
|
9866
|
+
const { arbi } = await resolveWorkspace();
|
|
9867
|
+
const data = await sdk.files.getFile(arbi, id);
|
|
9868
|
+
printJson(data);
|
|
9869
|
+
})()
|
|
9870
|
+
);
|
|
9871
|
+
files.command("delete <id>").description("Delete an ephemeral file").option("--dry-run", "Preview which file would be deleted (no SDK call)").action(
|
|
9872
|
+
(id, opts) => runAction(async () => {
|
|
9873
|
+
if (opts?.dryRun) {
|
|
9874
|
+
dryRun("delete file", id);
|
|
9875
|
+
return;
|
|
9876
|
+
}
|
|
9877
|
+
const { arbi } = await resolveWorkspace();
|
|
9878
|
+
await sdk.files.deleteFile(arbi, id);
|
|
9879
|
+
success(`Deleted: ${ref(id)}`);
|
|
9880
|
+
})()
|
|
9881
|
+
);
|
|
9882
|
+
files.command("content <id>").description("Fetch a file's raw bytes (writes to disk or stdout)").option("-o, --output <path>", 'Output file path (use "-" for stdout)').action(
|
|
9883
|
+
(id, opts) => runAction(async () => {
|
|
9884
|
+
const { arbi, loginResult } = await resolveWorkspace();
|
|
9885
|
+
const res = await sdk.files.getFileContent(
|
|
9886
|
+
{
|
|
9887
|
+
baseUrl: arbi.fetch.baseUrl,
|
|
9888
|
+
accessToken: loginResult.accessToken
|
|
9889
|
+
},
|
|
9890
|
+
id
|
|
9891
|
+
);
|
|
9892
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
9893
|
+
if (!opts.output || opts.output === "-") {
|
|
9894
|
+
process.stdout.write(buf);
|
|
9895
|
+
return;
|
|
9896
|
+
}
|
|
9897
|
+
fs5.writeFileSync(path5.resolve(opts.output), buf);
|
|
9898
|
+
success(`Wrote ${formatBytes(buf.length)} \u2192 ${opts.output}`);
|
|
9899
|
+
})()
|
|
9900
|
+
);
|
|
9901
|
+
files.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
9902
|
+
const tail = files.args ?? [];
|
|
9903
|
+
const subcommands = files.commands.map((c) => c.name());
|
|
9904
|
+
const firstNonFlag = tail.find((a) => !a.startsWith("-"));
|
|
9905
|
+
if (firstNonFlag && !subcommands.includes(firstNonFlag)) {
|
|
9906
|
+
suggestSubcommandAndExit("files", firstNonFlag, subcommands);
|
|
9907
|
+
}
|
|
9908
|
+
await files.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
9909
|
+
});
|
|
9910
|
+
}
|
|
9911
|
+
|
|
9912
|
+
// src/commands/usage.ts
|
|
9913
|
+
function registerUsageCommand(program2) {
|
|
9914
|
+
const usage = program2.command("usage").description("Show today's usage against the daily cap");
|
|
9915
|
+
usage.command("today").description("Today's tokens used / remaining (for the current user)").option("--json", "Output as JSON").action(
|
|
9916
|
+
(opts) => runAction(async () => {
|
|
9917
|
+
const { arbi } = await resolveAuth();
|
|
9918
|
+
const res = await arbi.fetch.GET("/v1/user/me/usage/today");
|
|
9919
|
+
if (res.error) {
|
|
9920
|
+
throw new Error(`Failed to fetch usage: ${JSON.stringify(res.error)}`);
|
|
9921
|
+
}
|
|
9922
|
+
const data = res.data;
|
|
9923
|
+
if (opts.json) {
|
|
9924
|
+
printJson(data);
|
|
9925
|
+
return;
|
|
9926
|
+
}
|
|
9927
|
+
for (const [k, v] of Object.entries(data)) {
|
|
9928
|
+
label(`${k}:`, typeof v === "object" ? JSON.stringify(v) : String(v));
|
|
9929
|
+
}
|
|
9930
|
+
dim("\nPass --json for machine-readable output.");
|
|
9931
|
+
})()
|
|
9932
|
+
);
|
|
9933
|
+
usage.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
9934
|
+
const tail = usage.args ?? [];
|
|
9935
|
+
const subcommands = usage.commands.map((c) => c.name());
|
|
9936
|
+
const firstNonFlag = tail.find((a) => !a.startsWith("-"));
|
|
9937
|
+
if (firstNonFlag && !subcommands.includes(firstNonFlag)) {
|
|
9938
|
+
suggestSubcommandAndExit("usage", firstNonFlag, subcommands);
|
|
9939
|
+
}
|
|
9940
|
+
await usage.commands.find((c) => c.name() === "today").parseAsync(tail, { from: "user" });
|
|
9941
|
+
});
|
|
9942
|
+
}
|
|
9943
|
+
init_prompts();
|
|
9944
|
+
function registerAuthCommand(program2) {
|
|
9945
|
+
const auth = program2.command("auth").description("Self-service account ops: profile, change-email, delete-account, sso-config");
|
|
9946
|
+
auth.command("profile").description("Show or update your display profile (given/family name, picture)").option("--name <given>", "New given name").option("--family <family>", "New family name (empty string clears it)").option(
|
|
9947
|
+
"--picture <path-or-url>",
|
|
9948
|
+
"Profile picture (local path \u2192 base64 inline; URL \u2192 stored verbatim)"
|
|
9949
|
+
).action(
|
|
9950
|
+
(opts) => runAction(async () => {
|
|
9951
|
+
const { arbi } = await resolveAuth();
|
|
9952
|
+
const noUpdates = !opts.name && opts.family === void 0 && !opts.picture;
|
|
9953
|
+
if (noUpdates) {
|
|
9954
|
+
const myExtId = arbi.session.getState().userExtId;
|
|
9955
|
+
const res2 = await arbi.fetch.GET("/v1/workspace/users");
|
|
9956
|
+
if (res2.error || !res2.data) {
|
|
9957
|
+
error(
|
|
9958
|
+
"No profile-read endpoint exists. To inspect: arbi status --json or arbi contacts --json"
|
|
9959
|
+
);
|
|
9960
|
+
process.exit(1);
|
|
9961
|
+
}
|
|
9962
|
+
const me = res2.data.find(
|
|
9963
|
+
(u) => u.external_id === myExtId
|
|
9964
|
+
);
|
|
9965
|
+
const profile = me ?? { external_id: myExtId };
|
|
9966
|
+
for (const [k, v] of Object.entries(profile)) {
|
|
9967
|
+
label(`${k}:`, typeof v === "object" ? JSON.stringify(v) : String(v));
|
|
9968
|
+
}
|
|
9969
|
+
dim("\nPass --name/--family/--picture to update.");
|
|
9970
|
+
return;
|
|
9971
|
+
}
|
|
9972
|
+
let picture = opts.picture;
|
|
9973
|
+
if (picture && !/^https?:|^data:/.test(picture)) {
|
|
9974
|
+
try {
|
|
9975
|
+
const bytes = fs5.readFileSync(path5.resolve(picture));
|
|
9976
|
+
const ext = picture.toLowerCase().split(".").pop();
|
|
9977
|
+
const mime = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : "image/jpeg";
|
|
9978
|
+
picture = `data:${mime};base64,${bytes.toString("base64")}`;
|
|
9979
|
+
} catch {
|
|
9980
|
+
error(`Cannot read picture file: ${picture}`);
|
|
9981
|
+
process.exit(3);
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
const body = {};
|
|
9985
|
+
if (opts.name !== void 0) body.given_name = opts.name;
|
|
9986
|
+
if (opts.family !== void 0) body.family_name = opts.family;
|
|
9987
|
+
if (picture !== void 0) body.picture = picture;
|
|
9988
|
+
const res = await arbi.fetch.PATCH("/v1/user/profile", { body });
|
|
9989
|
+
if (res.error) throw new Error(`Profile update failed: ${JSON.stringify(res.error)}`);
|
|
9990
|
+
success("Profile updated.");
|
|
9991
|
+
})()
|
|
9992
|
+
);
|
|
9993
|
+
auth.command("change-email <new-email>").description("Request a verification code for an email change (SSO users update at IdP)").action(
|
|
9994
|
+
(newEmail) => runAction(async () => {
|
|
9995
|
+
const { arbi } = await resolveAuth();
|
|
9996
|
+
const res = await arbi.fetch.POST("/v1/user/request-email-change", {
|
|
9997
|
+
body: { new_email: newEmail }
|
|
9998
|
+
});
|
|
9999
|
+
if (res.error) throw new Error(`request-email-change failed: ${JSON.stringify(res.error)}`);
|
|
10000
|
+
success(`Verification code sent to ${newEmail}.`);
|
|
10001
|
+
dim(`Confirm with: arbi auth confirm-email <code>`);
|
|
10002
|
+
})()
|
|
10003
|
+
);
|
|
10004
|
+
auth.command("confirm-email <code>").description("Confirm an email change with the code from change-email").option("--new-email <email>", "The new email being confirmed (required)").action(
|
|
10005
|
+
(code, opts) => runAction(async () => {
|
|
10006
|
+
if (!opts.newEmail) {
|
|
10007
|
+
error("Missing --new-email. The new address is required to complete the change.");
|
|
10008
|
+
process.exit(1);
|
|
10009
|
+
}
|
|
10010
|
+
const { arbi } = await resolveAuth();
|
|
10011
|
+
const res = await arbi.fetch.POST("/v1/user/confirm-email-change", {
|
|
10012
|
+
body: { new_email: opts.newEmail, verification_code: code }
|
|
10013
|
+
});
|
|
10014
|
+
if (res.error) throw new Error(`confirm-email-change failed: ${JSON.stringify(res.error)}`);
|
|
10015
|
+
success(`Email changed to ${opts.newEmail}.`);
|
|
10016
|
+
})()
|
|
10017
|
+
);
|
|
10018
|
+
auth.command("delete-account").description("Request account deletion (30-day grace window \u2014 use cancel-deletion to undo)").option("-y, --yes", "Skip confirmation (required in non-TTY shells)").option("--dry-run", "Preview the deletion request (no SDK call)").action(
|
|
10019
|
+
(opts) => runAction(async () => {
|
|
10020
|
+
const { arbi } = await resolveAuth();
|
|
10021
|
+
if (opts.dryRun) {
|
|
10022
|
+
const email = arbi.session.getState().userExtId ?? "(unknown)";
|
|
10023
|
+
dryRun("request account deletion (30-day grace)", email);
|
|
10024
|
+
return;
|
|
10025
|
+
}
|
|
10026
|
+
if (!opts.yes) {
|
|
10027
|
+
requireInteractive("Pass -y/--yes to confirm in non-TTY shells.");
|
|
10028
|
+
const confirmed = await promptConfirm(
|
|
10029
|
+
"Request deletion of this account (30-day grace)?",
|
|
10030
|
+
false
|
|
10031
|
+
);
|
|
10032
|
+
if (!confirmed) {
|
|
10033
|
+
console.log("Cancelled.");
|
|
10034
|
+
return;
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
const res = await arbi.fetch.POST("/v1/user/request-deletion", { body: {} });
|
|
10038
|
+
if (res.error) throw new Error(`request-deletion failed: ${JSON.stringify(res.error)}`);
|
|
10039
|
+
success("Deletion requested. You have 30 days to cancel with: arbi auth cancel-deletion");
|
|
10040
|
+
})()
|
|
10041
|
+
);
|
|
10042
|
+
auth.command("cancel-deletion").description("Cancel an in-flight account deletion request").action(
|
|
10043
|
+
() => runAction(async () => {
|
|
10044
|
+
const { arbi } = await resolveAuth();
|
|
10045
|
+
const res = await arbi.fetch.POST("/v1/user/cancel-deletion", { body: {} });
|
|
10046
|
+
if (res.error) throw new Error(`cancel-deletion failed: ${JSON.stringify(res.error)}`);
|
|
10047
|
+
success("Deletion cancelled. Account is active.");
|
|
10048
|
+
})()
|
|
10049
|
+
);
|
|
10050
|
+
auth.command("sso-config").description("Show SSO config for this deployment (sso_enabled, Auth0 domain, audience)").option("--json", "Output as JSON").action(
|
|
10051
|
+
(opts) => runAction(async () => {
|
|
10052
|
+
const { arbi } = await resolveAuth();
|
|
10053
|
+
const res = await arbi.fetch.GET("/v1/user/sso-config");
|
|
10054
|
+
if (res.error) throw new Error(`sso-config failed: ${JSON.stringify(res.error)}`);
|
|
10055
|
+
if (opts.json) {
|
|
10056
|
+
printJson(res.data);
|
|
10057
|
+
return;
|
|
10058
|
+
}
|
|
10059
|
+
for (const [k, v] of Object.entries(res.data)) {
|
|
10060
|
+
label(`${k}:`, typeof v === "object" ? JSON.stringify(v) : String(v));
|
|
10061
|
+
}
|
|
10062
|
+
dim("\nTo log in: arbi login --sso -e <email> -p <master-password>");
|
|
10063
|
+
dim("(--sso still needs --password \u2014 the master password is the E2E key seed.)");
|
|
10064
|
+
})()
|
|
10065
|
+
);
|
|
10066
|
+
auth.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
10067
|
+
const tail = auth.args ?? [];
|
|
10068
|
+
const subcommands = auth.commands.map((c) => c.name());
|
|
10069
|
+
const firstNonFlag = tail.find((a) => !a.startsWith("-"));
|
|
10070
|
+
if (firstNonFlag && !subcommands.includes(firstNonFlag)) {
|
|
10071
|
+
suggestSubcommandAndExit("auth", firstNonFlag, subcommands);
|
|
10072
|
+
}
|
|
10073
|
+
await auth.commands.find((c) => c.name() === "profile").parseAsync(tail, { from: "user" });
|
|
10074
|
+
});
|
|
10075
|
+
}
|
|
8746
10076
|
|
|
8747
10077
|
// src/index.ts
|
|
8748
10078
|
console.debug = () => {
|
|
@@ -8753,7 +10083,7 @@ console.info = (...args) => {
|
|
|
8753
10083
|
_origInfo(...args);
|
|
8754
10084
|
};
|
|
8755
10085
|
var program = new commander.Command();
|
|
8756
|
-
program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.
|
|
10086
|
+
program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.64").showHelpAfterError(true).showSuggestionAfterError(true);
|
|
8757
10087
|
registerConfigCommand(program);
|
|
8758
10088
|
registerLoginCommand(program);
|
|
8759
10089
|
registerRegisterCommand(program);
|
|
@@ -8785,6 +10115,41 @@ registerCompletionCommand(program);
|
|
|
8785
10115
|
registerListenCommand(program);
|
|
8786
10116
|
registerSessionCommand(program);
|
|
8787
10117
|
registerLocalCommand(program);
|
|
10118
|
+
registerNotificationsCommand(program);
|
|
10119
|
+
registerProjectCommand(program);
|
|
10120
|
+
registerFilesCommand(program);
|
|
10121
|
+
registerUsageCommand(program);
|
|
10122
|
+
registerAuthCommand(program);
|
|
10123
|
+
applyHelpGroups(program, {
|
|
10124
|
+
"Getting started:": [
|
|
10125
|
+
"quickstart",
|
|
10126
|
+
"login",
|
|
10127
|
+
"register",
|
|
10128
|
+
"logout",
|
|
10129
|
+
"status",
|
|
10130
|
+
"config",
|
|
10131
|
+
"completion"
|
|
10132
|
+
],
|
|
10133
|
+
"Workspaces:": ["workspaces", "workspace", "project"],
|
|
10134
|
+
"Documents:": ["docs", "doc", "add", "upload", "download"],
|
|
10135
|
+
"Search & ask:": ["ask", "find", "cite", "watch"],
|
|
10136
|
+
"Tagging:": ["tags", "doctags"],
|
|
10137
|
+
"Conversations & messaging:": ["convo", "dm", "listen", "notifications", "contacts"],
|
|
10138
|
+
"Agents & tasks:": ["agent", "agent-create", "task", "agentconfig", "session"],
|
|
10139
|
+
"Account:": ["auth", "usage", "files"],
|
|
10140
|
+
"System:": ["health", "models", "settings", "tui", "local", "update"]
|
|
10141
|
+
});
|
|
10142
|
+
function applyHelpGroups(prog, groups) {
|
|
10143
|
+
for (const [groupLabel, names] of Object.entries(groups)) {
|
|
10144
|
+
for (const name of names) {
|
|
10145
|
+
const cmd = prog.commands.find((c) => c.name() === name);
|
|
10146
|
+
const hg = cmd?.helpGroup;
|
|
10147
|
+
if (cmd && typeof hg === "function") {
|
|
10148
|
+
hg.call(cmd, groupLabel);
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10151
|
+
}
|
|
10152
|
+
}
|
|
8788
10153
|
var completionIdx = process.argv.indexOf("--get-completions");
|
|
8789
10154
|
if (completionIdx !== -1) {
|
|
8790
10155
|
const line = process.argv[completionIdx + 1] ?? "";
|