@prismer/sdk 1.9.22 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -94
- package/dist/cli.js +2006 -439
- package/dist/index.d.mts +579 -61
- package/dist/index.d.ts +579 -61
- package/dist/index.js +620 -83
- package/dist/index.mjs +615 -82
- package/package.json +4 -3
- package/smallicon +6 -0
package/dist/cli.js
CHANGED
|
@@ -36,8 +36,8 @@ __export(cli_exports, {
|
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(cli_exports);
|
|
38
38
|
var import_commander = require("commander");
|
|
39
|
-
var
|
|
40
|
-
var
|
|
39
|
+
var fs4 = __toESM(require("fs"));
|
|
40
|
+
var path4 = __toESM(require("path"));
|
|
41
41
|
var os2 = __toESM(require("os"));
|
|
42
42
|
var TOML2 = __toESM(require("@iarna/toml"));
|
|
43
43
|
|
|
@@ -163,7 +163,7 @@ var RealtimeWSClient = class extends TypedEmitter {
|
|
|
163
163
|
if (this._state === "connected" || this._state === "connecting") return;
|
|
164
164
|
this._state = "connecting";
|
|
165
165
|
this.intentionalClose = false;
|
|
166
|
-
return new Promise((
|
|
166
|
+
return new Promise((resolve3, reject) => {
|
|
167
167
|
try {
|
|
168
168
|
this.ws = new this.WS(this.wsUrl);
|
|
169
169
|
} catch (err) {
|
|
@@ -185,7 +185,7 @@ var RealtimeWSClient = class extends TypedEmitter {
|
|
|
185
185
|
this.emit("connected", void 0);
|
|
186
186
|
this.ws.removeEventListener("message", onFirstMessage);
|
|
187
187
|
this.ws.addEventListener("message", this.handleMessage);
|
|
188
|
-
|
|
188
|
+
resolve3();
|
|
189
189
|
}
|
|
190
190
|
} catch (_) {
|
|
191
191
|
}
|
|
@@ -257,12 +257,12 @@ var RealtimeWSClient = class extends TypedEmitter {
|
|
|
257
257
|
}
|
|
258
258
|
ping() {
|
|
259
259
|
const requestId = `ping-${++this.pingCounter}`;
|
|
260
|
-
return new Promise((
|
|
260
|
+
return new Promise((resolve3, reject) => {
|
|
261
261
|
const timer = setTimeout(() => {
|
|
262
262
|
this.pendingPings.delete(requestId);
|
|
263
263
|
reject(new Error("Ping timeout"));
|
|
264
264
|
}, 1e4);
|
|
265
|
-
this.pendingPings.set(requestId, { resolve:
|
|
265
|
+
this.pendingPings.set(requestId, { resolve: resolve3, timer });
|
|
266
266
|
this.sendRaw({ type: "ping", payload: { requestId } });
|
|
267
267
|
});
|
|
268
268
|
}
|
|
@@ -515,9 +515,9 @@ var WRITE_PATTERNS = [
|
|
|
515
515
|
{ method: "POST", pattern: /\/api\/im\/community\/posts\/[^/]+\/comments$/, opType: "community_comment" },
|
|
516
516
|
{ method: "POST", pattern: /\/api\/im\/community\/vote$/, opType: "community_vote" }
|
|
517
517
|
];
|
|
518
|
-
function matchWriteOp(method,
|
|
518
|
+
function matchWriteOp(method, path5) {
|
|
519
519
|
for (const { method: m, pattern, opType } of WRITE_PATTERNS) {
|
|
520
|
-
if (method === m && pattern.test(
|
|
520
|
+
if (method === m && pattern.test(path5)) return opType;
|
|
521
521
|
}
|
|
522
522
|
return null;
|
|
523
523
|
}
|
|
@@ -585,18 +585,18 @@ var OfflineManager = class extends OfflineEmitter {
|
|
|
585
585
|
/**
|
|
586
586
|
* Dispatch an IM request. Write ops go through outbox; reads check local cache.
|
|
587
587
|
*/
|
|
588
|
-
async dispatch(method,
|
|
589
|
-
const opType = matchWriteOp(method,
|
|
588
|
+
async dispatch(method, path5, body, query) {
|
|
589
|
+
const opType = matchWriteOp(method, path5);
|
|
590
590
|
if (opType) {
|
|
591
|
-
return this.dispatchWrite(opType, method,
|
|
591
|
+
return this.dispatchWrite(opType, method, path5, body, query);
|
|
592
592
|
}
|
|
593
593
|
if (method === "GET") {
|
|
594
|
-
const cached = await this.readFromCache(
|
|
594
|
+
const cached = await this.readFromCache(path5, query);
|
|
595
595
|
if (cached !== null) return cached;
|
|
596
596
|
}
|
|
597
597
|
try {
|
|
598
|
-
const result = await this.networkRequest(method,
|
|
599
|
-
if (method === "GET") this.cacheReadResult(
|
|
598
|
+
const result = await this.networkRequest(method, path5, body, query);
|
|
599
|
+
if (method === "GET") this.cacheReadResult(path5, query, result);
|
|
600
600
|
return result;
|
|
601
601
|
} catch {
|
|
602
602
|
if (!this._isOnline) {
|
|
@@ -606,7 +606,7 @@ var OfflineManager = class extends OfflineEmitter {
|
|
|
606
606
|
}
|
|
607
607
|
}
|
|
608
608
|
// ── Outbox: write operations ──────────────────────────────
|
|
609
|
-
async dispatchWrite(opType, method,
|
|
609
|
+
async dispatchWrite(opType, method, path5, body, query) {
|
|
610
610
|
const clientId = generateId();
|
|
611
611
|
const idempotencyKey = `sdk-${clientId}`;
|
|
612
612
|
let enrichedBody = body;
|
|
@@ -620,7 +620,7 @@ var OfflineManager = class extends OfflineEmitter {
|
|
|
620
620
|
let localMessage;
|
|
621
621
|
if (opType === "message.send" && body && typeof body === "object") {
|
|
622
622
|
const b = body;
|
|
623
|
-
const convIdMatch =
|
|
623
|
+
const convIdMatch = path5.match(/\/(?:messages|direct|groups)\/([^/]+)/);
|
|
624
624
|
const conversationId = convIdMatch?.[1] ?? "";
|
|
625
625
|
localMessage = {
|
|
626
626
|
id: `local-${clientId}`,
|
|
@@ -641,7 +641,7 @@ var OfflineManager = class extends OfflineEmitter {
|
|
|
641
641
|
id: clientId,
|
|
642
642
|
type: opType,
|
|
643
643
|
method,
|
|
644
|
-
path:
|
|
644
|
+
path: path5,
|
|
645
645
|
body: enrichedBody,
|
|
646
646
|
query,
|
|
647
647
|
status: "pending",
|
|
@@ -961,28 +961,28 @@ var OfflineManager = class extends OfflineEmitter {
|
|
|
961
961
|
return 0;
|
|
962
962
|
}
|
|
963
963
|
// ── Read cache ────────────────────────────────────────────
|
|
964
|
-
async readFromCache(
|
|
965
|
-
if (/\/api\/im\/conversations$/.test(
|
|
964
|
+
async readFromCache(path5, query) {
|
|
965
|
+
if (/\/api\/im\/conversations$/.test(path5)) {
|
|
966
966
|
const convos = await this.storage.getConversations({ limit: 50 });
|
|
967
967
|
if (convos.length > 0) return { ok: true, data: convos };
|
|
968
968
|
}
|
|
969
|
-
const msgMatch =
|
|
969
|
+
const msgMatch = path5.match(/\/api\/im\/messages\/([^/]+)$/);
|
|
970
970
|
if (msgMatch) {
|
|
971
971
|
const convId = msgMatch[1];
|
|
972
972
|
const limit = query?.limit ? parseInt(query.limit) : 50;
|
|
973
973
|
const messages = await this.storage.getMessages(convId, { limit, before: query?.before });
|
|
974
974
|
if (messages.length > 0) return { ok: true, data: messages };
|
|
975
975
|
}
|
|
976
|
-
if (/\/api\/im\/contacts$/.test(
|
|
976
|
+
if (/\/api\/im\/contacts$/.test(path5)) {
|
|
977
977
|
const contacts = await this.storage.getContacts();
|
|
978
978
|
if (contacts.length > 0) return { ok: true, data: contacts };
|
|
979
979
|
}
|
|
980
980
|
return null;
|
|
981
981
|
}
|
|
982
|
-
async cacheReadResult(
|
|
982
|
+
async cacheReadResult(path5, _query, result) {
|
|
983
983
|
if (!result?.ok || !result?.data) return;
|
|
984
984
|
try {
|
|
985
|
-
if (/\/api\/im\/conversations$/.test(
|
|
985
|
+
if (/\/api\/im\/conversations$/.test(path5) && Array.isArray(result.data)) {
|
|
986
986
|
const convos = result.data.map((c) => ({
|
|
987
987
|
id: c.id,
|
|
988
988
|
type: c.type ?? "direct",
|
|
@@ -996,7 +996,7 @@ var OfflineManager = class extends OfflineEmitter {
|
|
|
996
996
|
}));
|
|
997
997
|
await this.storage.putConversations(convos);
|
|
998
998
|
}
|
|
999
|
-
const msgMatch =
|
|
999
|
+
const msgMatch = path5.match(/\/api\/im\/messages\/([^/]+)$/);
|
|
1000
1000
|
if (msgMatch && Array.isArray(result.data)) {
|
|
1001
1001
|
const messages = result.data.map((m) => ({
|
|
1002
1002
|
id: m.id,
|
|
@@ -1011,7 +1011,7 @@ var OfflineManager = class extends OfflineEmitter {
|
|
|
1011
1011
|
}));
|
|
1012
1012
|
await this.storage.putMessages(messages);
|
|
1013
1013
|
}
|
|
1014
|
-
if (/\/api\/im\/contacts$/.test(
|
|
1014
|
+
if (/\/api\/im\/contacts$/.test(path5) && Array.isArray(result.data)) {
|
|
1015
1015
|
await this.storage.putContacts(result.data);
|
|
1016
1016
|
}
|
|
1017
1017
|
} catch {
|
|
@@ -1801,6 +1801,7 @@ var DirectClient = class {
|
|
|
1801
1801
|
content,
|
|
1802
1802
|
type: options?.type ?? "text",
|
|
1803
1803
|
metadata: options?.metadata,
|
|
1804
|
+
attachments: options?.attachments,
|
|
1804
1805
|
parentId: options?.parentId,
|
|
1805
1806
|
quotedMessageId: options?.quotedMessageId
|
|
1806
1807
|
});
|
|
@@ -1835,6 +1836,7 @@ var GroupsClient = class {
|
|
|
1835
1836
|
content,
|
|
1836
1837
|
type: options?.type ?? "text",
|
|
1837
1838
|
metadata: options?.metadata,
|
|
1839
|
+
attachments: options?.attachments,
|
|
1838
1840
|
parentId: options?.parentId,
|
|
1839
1841
|
quotedMessageId: options?.quotedMessageId
|
|
1840
1842
|
});
|
|
@@ -1913,6 +1915,7 @@ var MessagesClient = class {
|
|
|
1913
1915
|
content,
|
|
1914
1916
|
type: options?.type ?? "text",
|
|
1915
1917
|
metadata: options?.metadata,
|
|
1918
|
+
attachments: options?.attachments,
|
|
1916
1919
|
parentId: options?.parentId,
|
|
1917
1920
|
quotedMessageId: options?.quotedMessageId
|
|
1918
1921
|
});
|
|
@@ -1973,6 +1976,11 @@ var ContactsClient = class {
|
|
|
1973
1976
|
const query = {};
|
|
1974
1977
|
if (options?.type) query.type = options.type;
|
|
1975
1978
|
if (options?.capability) query.capability = options.capability;
|
|
1979
|
+
if (options?.status) query.status = options.status;
|
|
1980
|
+
if (options?.onlineOnly) query.onlineOnly = options.onlineOnly;
|
|
1981
|
+
if (options?.q) query.q = options.q;
|
|
1982
|
+
if (options?.limit) query.limit = options.limit;
|
|
1983
|
+
if (options?.offset) query.offset = options.offset;
|
|
1976
1984
|
return this._r("GET", "/api/im/discover", void 0, query);
|
|
1977
1985
|
}
|
|
1978
1986
|
// ─── Friend System (v1.8.0 P9) ─────────────────────────
|
|
@@ -2171,8 +2179,8 @@ var TasksClient = class {
|
|
|
2171
2179
|
return this._r("POST", `/api/im/tasks/${taskId}/complete`, options);
|
|
2172
2180
|
}
|
|
2173
2181
|
/** Fail a task with error */
|
|
2174
|
-
async fail(taskId,
|
|
2175
|
-
return this._r("POST", `/api/im/tasks/${taskId}/fail`, { error
|
|
2182
|
+
async fail(taskId, error, metadata) {
|
|
2183
|
+
return this._r("POST", `/api/im/tasks/${taskId}/fail`, { error, metadata });
|
|
2176
2184
|
}
|
|
2177
2185
|
/** Approve a completed task */
|
|
2178
2186
|
async approve(taskId) {
|
|
@@ -2186,6 +2194,32 @@ var TasksClient = class {
|
|
|
2186
2194
|
async cancel(taskId) {
|
|
2187
2195
|
return this._r("DELETE", `/api/im/tasks/${taskId}`);
|
|
2188
2196
|
}
|
|
2197
|
+
/**
|
|
2198
|
+
* v2.0 release 200 §6.1 — unified state-machine transition.
|
|
2199
|
+
*
|
|
2200
|
+
* Drives every kanban / approve / reject / cancel / blocked / retry /
|
|
2201
|
+
* restore action through one endpoint. The 5 legacy endpoints
|
|
2202
|
+
* (start/complete/approve/reject/cancel) remain for backward
|
|
2203
|
+
* compatibility but new integrations should prefer this entrypoint.
|
|
2204
|
+
*
|
|
2205
|
+
* Server responds 409 (`code: 'invalid-transition'`) if the requested
|
|
2206
|
+
* `to` is not in the TRANSITIONS matrix from the current status, or
|
|
2207
|
+
* 403 (`code: 'forbidden'`) if the actor's tier is not in the rule's
|
|
2208
|
+
* `allowedActors`.
|
|
2209
|
+
*/
|
|
2210
|
+
async transition(taskId, options) {
|
|
2211
|
+
return this._r("POST", `/api/im/tasks/${taskId}/transition`, options);
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* v2.0 release 200 §5.3 — admin escape-hatch.
|
|
2215
|
+
*
|
|
2216
|
+
* Bypasses the TRANSITIONS matrix. Restricted to workspace owner /
|
|
2217
|
+
* admin / trustTier>=4. Reason is required; the call is audit-logged
|
|
2218
|
+
* with `force_transition: true`. UI does NOT expose this — ops only.
|
|
2219
|
+
*/
|
|
2220
|
+
async forceTransition(taskId, options) {
|
|
2221
|
+
return this._r("POST", `/api/im/tasks/${taskId}/force-transition`, options);
|
|
2222
|
+
}
|
|
2189
2223
|
};
|
|
2190
2224
|
var MemoryClient = class {
|
|
2191
2225
|
constructor(_r) {
|
|
@@ -2314,9 +2348,118 @@ var SecurityClient = class {
|
|
|
2314
2348
|
return this._r("DELETE", `/api/im/conversations/${conversationId}/keys/${keyUserId}`);
|
|
2315
2349
|
}
|
|
2316
2350
|
};
|
|
2351
|
+
var EvolutionSkillsClient = class {
|
|
2352
|
+
constructor(_r) {
|
|
2353
|
+
this._r = _r;
|
|
2354
|
+
}
|
|
2355
|
+
/** List the skill catalog. Alias of search() for the v2.0 public surface. */
|
|
2356
|
+
async list(options) {
|
|
2357
|
+
return this.search(options);
|
|
2358
|
+
}
|
|
2359
|
+
/** Browse and search the skill catalog. */
|
|
2360
|
+
async search(options) {
|
|
2361
|
+
const query = {};
|
|
2362
|
+
if (options?.query) query.query = options.query;
|
|
2363
|
+
if (options?.category) query.category = options.category;
|
|
2364
|
+
if (options?.source) query.source = options.source;
|
|
2365
|
+
if (options?.compatibility) query.compatibility = options.compatibility;
|
|
2366
|
+
if (options?.sort) query.sort = options.sort;
|
|
2367
|
+
if (options?.page != null) query.page = String(options.page);
|
|
2368
|
+
if (options?.limit != null) query.limit = String(options.limit);
|
|
2369
|
+
return this._r("GET", "/api/im/skills/search", void 0, query);
|
|
2370
|
+
}
|
|
2371
|
+
/** Get skill catalog stats. */
|
|
2372
|
+
async stats() {
|
|
2373
|
+
return this._r("GET", "/api/im/skills/stats");
|
|
2374
|
+
}
|
|
2375
|
+
/** List available skill categories. */
|
|
2376
|
+
async categories() {
|
|
2377
|
+
return this._r("GET", "/api/im/skills/categories");
|
|
2378
|
+
}
|
|
2379
|
+
/** List trending skills. */
|
|
2380
|
+
async trending(limit) {
|
|
2381
|
+
const query = {};
|
|
2382
|
+
if (limit != null) query.limit = String(limit);
|
|
2383
|
+
return this._r("GET", "/api/im/skills/trending", void 0, query);
|
|
2384
|
+
}
|
|
2385
|
+
/** List skills created by the authenticated agent. */
|
|
2386
|
+
async created() {
|
|
2387
|
+
return this._r("GET", "/api/im/skills/created");
|
|
2388
|
+
}
|
|
2389
|
+
/** Get skill detail by slug or ID. */
|
|
2390
|
+
async get(slugOrId) {
|
|
2391
|
+
return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}`);
|
|
2392
|
+
}
|
|
2393
|
+
/** Get full SKILL.md content and package metadata. */
|
|
2394
|
+
async content(slugOrId) {
|
|
2395
|
+
return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}/content`);
|
|
2396
|
+
}
|
|
2397
|
+
/** Create/submit a workspace or community skill. */
|
|
2398
|
+
async create(input) {
|
|
2399
|
+
return this._r("POST", "/api/im/skills", input);
|
|
2400
|
+
}
|
|
2401
|
+
/** Update a skill. */
|
|
2402
|
+
async update(skillId, input) {
|
|
2403
|
+
return this._r("PATCH", `/api/im/skills/${encodeURIComponent(skillId)}`, input);
|
|
2404
|
+
}
|
|
2405
|
+
/** Soft-delete/deprecate a skill. */
|
|
2406
|
+
async delete(skillId) {
|
|
2407
|
+
return this._r("DELETE", `/api/im/skills/${encodeURIComponent(skillId)}`);
|
|
2408
|
+
}
|
|
2409
|
+
/** Install a skill for the authenticated agent. */
|
|
2410
|
+
async install(slugOrId, scope) {
|
|
2411
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, scope ? { scope } : void 0);
|
|
2412
|
+
}
|
|
2413
|
+
/** Uninstall a skill for the authenticated agent. */
|
|
2414
|
+
async uninstall(slugOrId, scope) {
|
|
2415
|
+
const query = scope ? { scope } : void 0;
|
|
2416
|
+
return this._r("DELETE", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, void 0, query);
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* List installed skills. When agentId is supplied, this uses the v2.0 Layer 5
|
|
2420
|
+
* route and includes daemon sync state; otherwise it keeps the legacy current-agent route.
|
|
2421
|
+
*/
|
|
2422
|
+
async installed(options) {
|
|
2423
|
+
const query = {};
|
|
2424
|
+
if (options?.workspaceId) query.workspaceId = options.workspaceId;
|
|
2425
|
+
if (options?.includeInactive) query.includeInactive = "true";
|
|
2426
|
+
if (options?.agentId) {
|
|
2427
|
+
return this._r("GET", `/api/im/agents/${encodeURIComponent(options.agentId)}/skills`, void 0, query);
|
|
2428
|
+
}
|
|
2429
|
+
return this._r("GET", "/api/im/skills/installed", void 0, query);
|
|
2430
|
+
}
|
|
2431
|
+
/** Install a skill to a specific agent. */
|
|
2432
|
+
async installForAgent(agentId, skillIdOrSlug, options) {
|
|
2433
|
+
return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/skills`, {
|
|
2434
|
+
skillId: skillIdOrSlug,
|
|
2435
|
+
...options
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
/** Disable/uninstall a skill from a specific agent. Built-ins are disabled cloud-side. */
|
|
2439
|
+
async uninstallFromAgent(agentId, skillIdOrSlug, options) {
|
|
2440
|
+
return this._r("DELETE", `/api/im/agents/${encodeURIComponent(agentId)}/skills`, {
|
|
2441
|
+
skillId: skillIdOrSlug,
|
|
2442
|
+
workspaceId: options?.workspaceId
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
/** List skills whose daemon sync state is not current. */
|
|
2446
|
+
async pending(agentId, workspaceId) {
|
|
2447
|
+
const query = workspaceId ? { workspaceId } : void 0;
|
|
2448
|
+
return this._r("GET", `/api/im/agents/${encodeURIComponent(agentId)}/skills/pending`, void 0, query);
|
|
2449
|
+
}
|
|
2450
|
+
/** Acknowledge daemon sync for an installed skill. */
|
|
2451
|
+
async ack(agentId, input) {
|
|
2452
|
+
return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/skills/ack`, input);
|
|
2453
|
+
}
|
|
2454
|
+
/** Star a skill. */
|
|
2455
|
+
async star(skillId) {
|
|
2456
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2317
2459
|
var EvolutionClient = class {
|
|
2318
2460
|
constructor(_r) {
|
|
2319
2461
|
this._r = _r;
|
|
2462
|
+
this.skills = new EvolutionSkillsClient(_r);
|
|
2320
2463
|
}
|
|
2321
2464
|
// ── Public endpoints (no auth required) ──
|
|
2322
2465
|
/** Get evolution stats */
|
|
@@ -2446,7 +2589,7 @@ var EvolutionClient = class {
|
|
|
2446
2589
|
const { outcome, score, summary, strategy_used, scope, ...analyzeOpts } = options;
|
|
2447
2590
|
const analysis = await this.analyze({ ...analyzeOpts, ...scope ? { scope } : {} });
|
|
2448
2591
|
if (!analysis.ok || !analysis.data) {
|
|
2449
|
-
return { ok: false, error: analysis.error };
|
|
2592
|
+
return { ok: false, ...analysis.error ? { error: analysis.error } : {} };
|
|
2450
2593
|
}
|
|
2451
2594
|
const data = analysis.data;
|
|
2452
2595
|
const geneId = data.gene_id;
|
|
@@ -2560,39 +2703,35 @@ var EvolutionClient = class {
|
|
|
2560
2703
|
}
|
|
2561
2704
|
/** Search skills catalog */
|
|
2562
2705
|
async searchSkills(options) {
|
|
2563
|
-
|
|
2564
|
-
if (options?.query) q.query = options.query;
|
|
2565
|
-
if (options?.category) q.category = options.category;
|
|
2566
|
-
if (options?.limit != null) q.limit = String(options.limit);
|
|
2567
|
-
return this._r("GET", "/api/im/skills/search", void 0, q);
|
|
2706
|
+
return this.skills.search(options);
|
|
2568
2707
|
}
|
|
2569
2708
|
/** Get skill catalog stats */
|
|
2570
2709
|
async getSkillStats() {
|
|
2571
|
-
return this.
|
|
2710
|
+
return this.skills.stats();
|
|
2572
2711
|
}
|
|
2573
2712
|
/** Install a skill — creates Gene + returns content + install guide */
|
|
2574
2713
|
async installSkill(slugOrId, scope) {
|
|
2575
|
-
return this.
|
|
2714
|
+
return this.skills.install(slugOrId, scope);
|
|
2576
2715
|
}
|
|
2577
2716
|
/** Uninstall a skill */
|
|
2578
|
-
async uninstallSkill(slugOrId) {
|
|
2579
|
-
return this.
|
|
2717
|
+
async uninstallSkill(slugOrId, scope) {
|
|
2718
|
+
return this.skills.uninstall(slugOrId, scope);
|
|
2580
2719
|
}
|
|
2581
2720
|
/** List installed skills for this agent */
|
|
2582
|
-
async installedSkills() {
|
|
2583
|
-
return this.
|
|
2721
|
+
async installedSkills(options) {
|
|
2722
|
+
return this.skills.installed(options);
|
|
2584
2723
|
}
|
|
2585
2724
|
/** Get full skill content (SKILL.md + package info) */
|
|
2586
2725
|
async getSkillContent(slugOrId) {
|
|
2587
|
-
return this.
|
|
2726
|
+
return this.skills.content(slugOrId);
|
|
2588
2727
|
}
|
|
2589
2728
|
/** Create/submit a community skill */
|
|
2590
2729
|
async createSkill(input) {
|
|
2591
|
-
return this.
|
|
2730
|
+
return this.skills.create(input);
|
|
2592
2731
|
}
|
|
2593
2732
|
/** Star a skill (increment community rating) */
|
|
2594
2733
|
async starSkill(skillId) {
|
|
2595
|
-
return this.
|
|
2734
|
+
return this.skills.star(skillId);
|
|
2596
2735
|
}
|
|
2597
2736
|
/**
|
|
2598
2737
|
* Install a skill and write SKILL.md to local filesystem.
|
|
@@ -2602,53 +2741,60 @@ var EvolutionClient = class {
|
|
|
2602
2741
|
*/
|
|
2603
2742
|
async installSkillLocal(slugOrId, options) {
|
|
2604
2743
|
const result = await this.installSkill(slugOrId);
|
|
2605
|
-
if (!result.ok || !result.data)
|
|
2606
|
-
|
|
2744
|
+
if (!result.ok || !result.data) {
|
|
2745
|
+
return result;
|
|
2746
|
+
}
|
|
2747
|
+
const installData = result.data;
|
|
2748
|
+
const withLocalPaths = (localPaths2) => ({
|
|
2749
|
+
ok: true,
|
|
2750
|
+
data: { ...installData, localPaths: localPaths2 }
|
|
2751
|
+
});
|
|
2752
|
+
let content = installData.skill?.content || "";
|
|
2607
2753
|
if (!content) {
|
|
2608
2754
|
const contentResult = await this.getSkillContent(slugOrId);
|
|
2609
2755
|
content = contentResult.data?.content || "";
|
|
2610
2756
|
}
|
|
2611
2757
|
if (!content) {
|
|
2612
|
-
return
|
|
2758
|
+
return withLocalPaths([]);
|
|
2613
2759
|
}
|
|
2614
|
-
const rawSlug =
|
|
2760
|
+
const rawSlug = installData.skill?.slug || slugOrId;
|
|
2615
2761
|
const slug = rawSlug.replace(/[\/\\]/g, "").replace(/\.\./g, "");
|
|
2616
2762
|
if (!slug) {
|
|
2617
|
-
return
|
|
2763
|
+
return withLocalPaths([]);
|
|
2618
2764
|
}
|
|
2619
2765
|
const localPaths = [];
|
|
2620
2766
|
try {
|
|
2621
|
-
const
|
|
2622
|
-
const
|
|
2767
|
+
const fs5 = await import("fs");
|
|
2768
|
+
const path5 = await import("path");
|
|
2623
2769
|
const os3 = await import("os");
|
|
2624
2770
|
const home = os3.homedir();
|
|
2625
|
-
const pluginBase = process.env.PRISMER_PLUGIN_DIR ||
|
|
2771
|
+
const pluginBase = process.env.PRISMER_PLUGIN_DIR || path5.join(home, ".claude", "plugins", "prismer");
|
|
2626
2772
|
const platformPaths = options?.project ? {
|
|
2627
|
-
"claude-code":
|
|
2628
|
-
"openclaw":
|
|
2629
|
-
"opencode":
|
|
2630
|
-
"plugin":
|
|
2773
|
+
"claude-code": path5.join(options.projectRoot || ".", ".claude", "skills", slug),
|
|
2774
|
+
"openclaw": path5.join(options.projectRoot || ".", "skills", slug),
|
|
2775
|
+
"opencode": path5.join(options.projectRoot || ".", ".opencode", "skills", slug),
|
|
2776
|
+
"plugin": path5.join(options.projectRoot || ".", ".claude", "plugins", "prismer", "skills", slug)
|
|
2631
2777
|
} : {
|
|
2632
|
-
"claude-code":
|
|
2633
|
-
"openclaw":
|
|
2634
|
-
"opencode":
|
|
2635
|
-
"plugin":
|
|
2778
|
+
"claude-code": path5.join(home, ".claude", "skills", slug),
|
|
2779
|
+
"openclaw": path5.join(home, ".openclaw", "skills", slug),
|
|
2780
|
+
"opencode": path5.join(home, ".config", "opencode", "skills", slug),
|
|
2781
|
+
"plugin": path5.join(pluginBase, "skills", slug)
|
|
2636
2782
|
};
|
|
2637
|
-
const targets = options?.platforms
|
|
2783
|
+
const targets = options?.platforms ?? Object.keys(platformPaths);
|
|
2638
2784
|
for (const platform of targets) {
|
|
2639
2785
|
const dir = platformPaths[platform];
|
|
2640
2786
|
if (!dir) continue;
|
|
2641
2787
|
try {
|
|
2642
|
-
|
|
2643
|
-
const filePath =
|
|
2644
|
-
|
|
2788
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
2789
|
+
const filePath = path5.join(dir, "SKILL.md");
|
|
2790
|
+
fs5.writeFileSync(filePath, content, "utf-8");
|
|
2645
2791
|
localPaths.push(filePath);
|
|
2646
2792
|
} catch {
|
|
2647
2793
|
}
|
|
2648
2794
|
}
|
|
2649
2795
|
} catch {
|
|
2650
2796
|
}
|
|
2651
|
-
return
|
|
2797
|
+
return withLocalPaths(localPaths);
|
|
2652
2798
|
}
|
|
2653
2799
|
/**
|
|
2654
2800
|
* Uninstall a skill and remove local SKILL.md files.
|
|
@@ -2656,24 +2802,29 @@ var EvolutionClient = class {
|
|
|
2656
2802
|
async uninstallSkillLocal(slugOrId) {
|
|
2657
2803
|
const result = await this.uninstallSkill(slugOrId);
|
|
2658
2804
|
const removedPaths = [];
|
|
2805
|
+
const withRemoved = (ok, paths) => ({
|
|
2806
|
+
ok: result.ok,
|
|
2807
|
+
...result.error ? { error: result.error } : {},
|
|
2808
|
+
data: { uninstalled: ok, removedPaths: paths }
|
|
2809
|
+
});
|
|
2659
2810
|
const slug = safeSlug(slugOrId);
|
|
2660
|
-
if (!slug) return
|
|
2811
|
+
if (!slug) return withRemoved(result.data?.uninstalled ?? false, removedPaths);
|
|
2661
2812
|
try {
|
|
2662
|
-
const
|
|
2663
|
-
const
|
|
2813
|
+
const fs5 = await import("fs");
|
|
2814
|
+
const path5 = await import("path");
|
|
2664
2815
|
const os3 = await import("os");
|
|
2665
2816
|
const home = os3.homedir();
|
|
2666
|
-
const pluginBase = process.env.PRISMER_PLUGIN_DIR ||
|
|
2817
|
+
const pluginBase = process.env.PRISMER_PLUGIN_DIR || path5.join(home, ".claude", "plugins", "prismer");
|
|
2667
2818
|
const dirs = [
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2819
|
+
path5.join(home, ".claude", "skills", slug),
|
|
2820
|
+
path5.join(home, ".openclaw", "skills", slug),
|
|
2821
|
+
path5.join(home, ".config", "opencode", "skills", slug),
|
|
2822
|
+
path5.join(pluginBase, "skills", slug)
|
|
2672
2823
|
];
|
|
2673
2824
|
for (const dir of dirs) {
|
|
2674
2825
|
try {
|
|
2675
|
-
if (
|
|
2676
|
-
|
|
2826
|
+
if (fs5.existsSync(dir)) {
|
|
2827
|
+
fs5.rmSync(dir, { recursive: true });
|
|
2677
2828
|
removedPaths.push(dir);
|
|
2678
2829
|
}
|
|
2679
2830
|
} catch {
|
|
@@ -2681,7 +2832,7 @@ var EvolutionClient = class {
|
|
|
2681
2832
|
}
|
|
2682
2833
|
} catch {
|
|
2683
2834
|
}
|
|
2684
|
-
return
|
|
2835
|
+
return withRemoved(result.data?.uninstalled ?? false, removedPaths);
|
|
2685
2836
|
}
|
|
2686
2837
|
/**
|
|
2687
2838
|
* Sync all installed skills to local filesystem.
|
|
@@ -2710,25 +2861,25 @@ var EvolutionClient = class {
|
|
|
2710
2861
|
failed++;
|
|
2711
2862
|
continue;
|
|
2712
2863
|
}
|
|
2713
|
-
const
|
|
2714
|
-
const
|
|
2864
|
+
const fs5 = await import("fs");
|
|
2865
|
+
const path5 = await import("path");
|
|
2715
2866
|
const os3 = await import("os");
|
|
2716
2867
|
const home = os3.homedir();
|
|
2717
|
-
const pluginBase = process.env.PRISMER_PLUGIN_DIR ||
|
|
2868
|
+
const pluginBase = process.env.PRISMER_PLUGIN_DIR || path5.join(home, ".claude", "plugins", "prismer");
|
|
2718
2869
|
const platformPaths = {
|
|
2719
|
-
"claude-code":
|
|
2720
|
-
"openclaw":
|
|
2721
|
-
"opencode":
|
|
2722
|
-
"plugin":
|
|
2870
|
+
"claude-code": path5.join(home, ".claude", "skills", slug),
|
|
2871
|
+
"openclaw": path5.join(home, ".openclaw", "skills", slug),
|
|
2872
|
+
"opencode": path5.join(home, ".config", "opencode", "skills", slug),
|
|
2873
|
+
"plugin": path5.join(pluginBase, "skills", slug)
|
|
2723
2874
|
};
|
|
2724
|
-
const targets = options?.platforms
|
|
2875
|
+
const targets = options?.platforms ?? Object.keys(platformPaths);
|
|
2725
2876
|
for (const platform of targets) {
|
|
2726
2877
|
const dir = platformPaths[platform];
|
|
2727
2878
|
if (!dir) continue;
|
|
2728
2879
|
try {
|
|
2729
|
-
|
|
2730
|
-
const filePath =
|
|
2731
|
-
|
|
2880
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
2881
|
+
const filePath = path5.join(dir, "SKILL.md");
|
|
2882
|
+
fs5.writeFileSync(filePath, content, "utf-8");
|
|
2732
2883
|
paths.push(filePath);
|
|
2733
2884
|
} catch {
|
|
2734
2885
|
}
|
|
@@ -2770,12 +2921,34 @@ var EvolutionClient = class {
|
|
|
2770
2921
|
if (since != null) query.since = String(since);
|
|
2771
2922
|
return this._r("GET", "/api/im/evolution/sync/snapshot", void 0, query);
|
|
2772
2923
|
}
|
|
2773
|
-
/**
|
|
2924
|
+
/**
|
|
2925
|
+
* Bidirectional sync: push local outcomes and pull remote updates.
|
|
2926
|
+
*
|
|
2927
|
+
* Accepts either the flat shape (`pushOutcomes` / `pullSince`) used by older
|
|
2928
|
+
* callers or the nested shape (`push` / `pull`) that mirrors the wire format
|
|
2929
|
+
* expected by `POST /api/im/evolution/sync`. The nested shape is preferred for
|
|
2930
|
+
* new code because it lets you pin a scope per-side.
|
|
2931
|
+
*/
|
|
2774
2932
|
async sync(options) {
|
|
2775
2933
|
const body = {};
|
|
2776
|
-
|
|
2777
|
-
if (
|
|
2778
|
-
|
|
2934
|
+
const outcomes = options?.push?.outcomes ?? options?.pushOutcomes;
|
|
2935
|
+
if (outcomes) {
|
|
2936
|
+
body.push = {
|
|
2937
|
+
outcomes,
|
|
2938
|
+
...options?.push?.scope ? { scope: options.push.scope } : {},
|
|
2939
|
+
...options?.push?.workspaceId ? { workspaceId: options.push.workspaceId } : {}
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
const since = options?.pull?.since ?? options?.pullSince;
|
|
2943
|
+
if (since != null) {
|
|
2944
|
+
body.pull = {
|
|
2945
|
+
since,
|
|
2946
|
+
...options?.pull?.scope ? { scope: options.pull.scope } : {}
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
const query = {};
|
|
2950
|
+
if (options?.scope) query.scope = options.scope;
|
|
2951
|
+
return this._r("POST", "/api/im/evolution/sync", body, query);
|
|
2779
2952
|
}
|
|
2780
2953
|
};
|
|
2781
2954
|
function safeSlug(input) {
|
|
@@ -2819,6 +2992,26 @@ var WorkspacesClient = class {
|
|
|
2819
2992
|
async archive(workspaceId) {
|
|
2820
2993
|
return this._r("DELETE", `/api/im/workspaces/${workspaceId}`);
|
|
2821
2994
|
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Get the workspace's orchestrator agent (Chief of Staff) — readable by any
|
|
2997
|
+
* member. Returns `{ workspace, orchestrator: null }` when no active
|
|
2998
|
+
* appointment exists. See release 200 §4.
|
|
2999
|
+
*/
|
|
3000
|
+
async getOrchestrator(workspaceId) {
|
|
3001
|
+
return this._r("GET", `/api/im/workspaces/${workspaceId}/orchestrator`);
|
|
3002
|
+
}
|
|
3003
|
+
/**
|
|
3004
|
+
* Appoint an agent as the workspace's orchestrator. Owner-only. If an
|
|
3005
|
+
* orchestrator is already active, this auto-revokes the previous one in the
|
|
3006
|
+
* same UPDATE.
|
|
3007
|
+
*/
|
|
3008
|
+
async appointOrchestrator(workspaceId, agentImUserId) {
|
|
3009
|
+
return this._r("POST", `/api/im/workspaces/${workspaceId}/orchestrator`, { agentImUserId });
|
|
3010
|
+
}
|
|
3011
|
+
/** Revoke the workspace's current orchestrator. Owner-only. Idempotent. */
|
|
3012
|
+
async revokeOrchestrator(workspaceId) {
|
|
3013
|
+
return this._r("DELETE", `/api/im/workspaces/${workspaceId}/orchestrator`);
|
|
3014
|
+
}
|
|
2822
3015
|
};
|
|
2823
3016
|
var WorkspaceFilesClient = class {
|
|
2824
3017
|
constructor(_r) {
|
|
@@ -2838,8 +3031,8 @@ var WorkspaceFilesClient = class {
|
|
|
2838
3031
|
return this._r("POST", `/api/im/workspaces/${workspaceId}/files`, options);
|
|
2839
3032
|
}
|
|
2840
3033
|
/** Soft-delete the active binding at `path`. */
|
|
2841
|
-
async delete(workspaceId,
|
|
2842
|
-
return this._r("DELETE", `/api/im/workspaces/${workspaceId}/files`, void 0, { path:
|
|
3034
|
+
async delete(workspaceId, path5) {
|
|
3035
|
+
return this._r("DELETE", `/api/im/workspaces/${workspaceId}/files`, void 0, { path: path5 });
|
|
2843
3036
|
}
|
|
2844
3037
|
/** Daemon delta-sync workspace files since an ISO timestamp. */
|
|
2845
3038
|
async sync(workspaceId, since) {
|
|
@@ -2852,6 +3045,83 @@ var WorkspaceFilesClient = class {
|
|
|
2852
3045
|
return this._r("GET", `/api/im/workspaces/${workspaceId}/files/${fileId}/history`);
|
|
2853
3046
|
}
|
|
2854
3047
|
};
|
|
3048
|
+
var MAX_IM_ASSET_BYTES = 1024 * 1024 * 1024;
|
|
3049
|
+
var DIRECT_ASSET_UPLOAD_FALLBACK_STATUSES = /* @__PURE__ */ new Set([404, 501, 503]);
|
|
3050
|
+
function toHex(bytes) {
|
|
3051
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3052
|
+
}
|
|
3053
|
+
async function sha256BytesHex(bytes) {
|
|
3054
|
+
if (globalThis.crypto?.subtle) {
|
|
3055
|
+
const ab = new ArrayBuffer(bytes.byteLength);
|
|
3056
|
+
new Uint8Array(ab).set(bytes);
|
|
3057
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", ab);
|
|
3058
|
+
return toHex(new Uint8Array(digest));
|
|
3059
|
+
}
|
|
3060
|
+
const { createHash } = await import("crypto");
|
|
3061
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
3062
|
+
}
|
|
3063
|
+
function bytesToBlob(bytes, mimeType) {
|
|
3064
|
+
const ab = new ArrayBuffer(bytes.byteLength);
|
|
3065
|
+
new Uint8Array(ab).set(bytes);
|
|
3066
|
+
return new Blob([ab], { type: mimeType });
|
|
3067
|
+
}
|
|
3068
|
+
function isNamedFile(input) {
|
|
3069
|
+
return typeof File !== "undefined" && input instanceof File;
|
|
3070
|
+
}
|
|
3071
|
+
function normalizeStringHeaders(value) {
|
|
3072
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3073
|
+
const out = {};
|
|
3074
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
3075
|
+
if (typeof raw === "string") out[key] = raw;
|
|
3076
|
+
}
|
|
3077
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
3078
|
+
}
|
|
3079
|
+
function isDirectAssetUploadPlan(value) {
|
|
3080
|
+
if (!value || typeof value !== "object") return false;
|
|
3081
|
+
const plan = value;
|
|
3082
|
+
if (plan.mode === "single") {
|
|
3083
|
+
return typeof plan.uploadUrl === "string" && typeof plan.bucket === "string" && typeof plan.key === "string";
|
|
3084
|
+
}
|
|
3085
|
+
if (plan.mode === "multipart") {
|
|
3086
|
+
return typeof plan.bucket === "string" && typeof plan.key === "string" && typeof plan.uploadId === "string" && typeof plan.partSizeBytes === "number" && Array.isArray(plan.parts) && plan.parts.every((part) => {
|
|
3087
|
+
const item = part;
|
|
3088
|
+
return typeof item.url === "string" && Number.isInteger(item.partNumber);
|
|
3089
|
+
});
|
|
3090
|
+
}
|
|
3091
|
+
return false;
|
|
3092
|
+
}
|
|
3093
|
+
function assetUploadError(code, message) {
|
|
3094
|
+
return { ok: false, error: { code, message } };
|
|
3095
|
+
}
|
|
3096
|
+
async function normalizeAssetUploadInput(input, options) {
|
|
3097
|
+
let bytes;
|
|
3098
|
+
let fileName;
|
|
3099
|
+
if (typeof input === "string") {
|
|
3100
|
+
const fs5 = await import("fs");
|
|
3101
|
+
const path5 = await import("path");
|
|
3102
|
+
const buf = await fs5.promises.readFile(input);
|
|
3103
|
+
bytes = new Uint8Array(buf);
|
|
3104
|
+
fileName = options.fileName || path5.basename(input);
|
|
3105
|
+
} else if (typeof Blob !== "undefined" && input instanceof Blob) {
|
|
3106
|
+
const ab = await input.arrayBuffer();
|
|
3107
|
+
bytes = new Uint8Array(ab);
|
|
3108
|
+
fileName = options.fileName || (isNamedFile(input) ? input.name : "");
|
|
3109
|
+
if (!fileName) throw new Error("fileName is required when uploading Blob without name");
|
|
3110
|
+
} else if (input instanceof Uint8Array) {
|
|
3111
|
+
bytes = input;
|
|
3112
|
+
fileName = options.fileName || "";
|
|
3113
|
+
if (!fileName) throw new Error("fileName is required when uploading Buffer or Uint8Array");
|
|
3114
|
+
} else {
|
|
3115
|
+
throw new Error("Unsupported input type");
|
|
3116
|
+
}
|
|
3117
|
+
const sizeBytes = bytes.byteLength;
|
|
3118
|
+
if (sizeBytes > MAX_IM_ASSET_BYTES) {
|
|
3119
|
+
throw new Error("Asset exceeds 1 GB cap");
|
|
3120
|
+
}
|
|
3121
|
+
const mimeType = options.mimeType || guessMimeType(fileName);
|
|
3122
|
+
const contentHash = await sha256BytesHex(bytes);
|
|
3123
|
+
return { bytes, fileName, mimeType, sizeBytes, contentHash };
|
|
3124
|
+
}
|
|
2855
3125
|
var AssetsClient = class {
|
|
2856
3126
|
constructor(_r, _baseUrl, _fetchFn, _getAuthHeaders) {
|
|
2857
3127
|
this._r = _r;
|
|
@@ -2918,57 +3188,130 @@ var AssetsClient = class {
|
|
|
2918
3188
|
};
|
|
2919
3189
|
}
|
|
2920
3190
|
/**
|
|
2921
|
-
* Upload bytes as an asset
|
|
2922
|
-
*
|
|
3191
|
+
* Upload bytes as an asset. Uses direct-to-S3 upload when the server exposes
|
|
3192
|
+
* `/assets/direct-upload/*`; falls back to legacy multipart POST for local
|
|
3193
|
+
* filesystem mode and older deployments. The client always sends SHA-256 for
|
|
3194
|
+
* server-side byte integrity checks. Hard cap: 1 GB.
|
|
2923
3195
|
*/
|
|
2924
3196
|
async upload(input, options) {
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
if (
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
3197
|
+
const normalized = await normalizeAssetUploadInput(input, options);
|
|
3198
|
+
const direct = await this._tryDirectUpload(normalized, options);
|
|
3199
|
+
if (direct) return direct;
|
|
3200
|
+
return this._uploadMultipart(normalized, options);
|
|
3201
|
+
}
|
|
3202
|
+
async _tryDirectUpload(file, options) {
|
|
3203
|
+
const init = await this._postAssetJson("/direct-upload/init", {
|
|
3204
|
+
workspaceId: options.workspaceId,
|
|
3205
|
+
filename: file.fileName,
|
|
3206
|
+
mime: file.mimeType,
|
|
3207
|
+
sizeBytes: file.sizeBytes,
|
|
3208
|
+
contentHash: file.contentHash
|
|
3209
|
+
});
|
|
3210
|
+
if (!init.response.ok) {
|
|
3211
|
+
if (DIRECT_ASSET_UPLOAD_FALLBACK_STATUSES.has(init.response.status)) return null;
|
|
3212
|
+
return init.data && init.data.ok === false ? {
|
|
3213
|
+
ok: false,
|
|
3214
|
+
error: init.data.error ?? {
|
|
3215
|
+
code: "http_error",
|
|
3216
|
+
message: `Direct upload init failed (${init.response.status})`
|
|
3217
|
+
}
|
|
3218
|
+
} : assetUploadError("http_error", `Direct upload init failed (${init.response.status})`);
|
|
3219
|
+
}
|
|
3220
|
+
if (!isDirectAssetUploadPlan(init.data?.data)) return null;
|
|
3221
|
+
const plan = init.data.data;
|
|
3222
|
+
try {
|
|
3223
|
+
const parts = await this._putDirectUploadBytes(plan, file, options.onProgress);
|
|
3224
|
+
const complete = await this._postAssetJson("/direct-upload/complete", {
|
|
3225
|
+
workspaceId: options.workspaceId,
|
|
3226
|
+
filename: file.fileName,
|
|
3227
|
+
mime: file.mimeType,
|
|
3228
|
+
sizeBytes: file.sizeBytes,
|
|
3229
|
+
contentHash: file.contentHash,
|
|
3230
|
+
kind: options.kind,
|
|
3231
|
+
metadata: options.metadata,
|
|
3232
|
+
sourceTaskId: options.sourceTaskId,
|
|
3233
|
+
sourceAgentImUserId: options.sourceAgentImUserId,
|
|
3234
|
+
bucket: plan.bucket,
|
|
3235
|
+
key: plan.key,
|
|
3236
|
+
...plan.mode === "multipart" ? { uploadId: plan.uploadId, parts: parts ?? [] } : {}
|
|
3237
|
+
});
|
|
3238
|
+
if (!complete.response.ok) {
|
|
3239
|
+
return complete.data && complete.data.ok === false ? complete.data : assetUploadError("http_error", `Direct upload complete failed (${complete.response.status})`);
|
|
3240
|
+
}
|
|
3241
|
+
if (!complete.data) {
|
|
3242
|
+
return assetUploadError("invalid_response", "Direct upload complete returned an empty response");
|
|
3243
|
+
}
|
|
3244
|
+
return complete.data;
|
|
3245
|
+
} catch {
|
|
3246
|
+
return null;
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
async _postAssetJson(path5, body) {
|
|
3250
|
+
const response = await this._fetchFn(`${this._baseUrl}/api/im/assets${path5}`, {
|
|
3251
|
+
method: "POST",
|
|
3252
|
+
headers: {
|
|
3253
|
+
...this._getAuthHeaders(),
|
|
3254
|
+
"Content-Type": "application/json",
|
|
3255
|
+
Accept: "application/json"
|
|
3256
|
+
},
|
|
3257
|
+
body: JSON.stringify(body)
|
|
3258
|
+
});
|
|
3259
|
+
const data = await response.json().catch(() => null);
|
|
3260
|
+
return { response, data };
|
|
3261
|
+
}
|
|
3262
|
+
async _putDirectUploadBytes(plan, file, onProgress) {
|
|
3263
|
+
if (plan.mode === "single") {
|
|
3264
|
+
const response = await this._fetchFn(plan.uploadUrl, {
|
|
3265
|
+
method: plan.method ?? "PUT",
|
|
3266
|
+
headers: normalizeStringHeaders(plan.headers),
|
|
3267
|
+
body: bytesToBlob(file.bytes, file.mimeType)
|
|
3268
|
+
});
|
|
3269
|
+
if (!response.ok) throw new Error(`signed PUT failed (${response.status})`);
|
|
3270
|
+
onProgress?.(file.sizeBytes, file.sizeBytes);
|
|
3271
|
+
return null;
|
|
2944
3272
|
}
|
|
2945
|
-
const
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3273
|
+
const completedParts = [];
|
|
3274
|
+
let uploaded = 0;
|
|
3275
|
+
for (const part of plan.parts) {
|
|
3276
|
+
const start = (part.partNumber - 1) * plan.partSizeBytes;
|
|
3277
|
+
const end = Math.min(start + plan.partSizeBytes, file.sizeBytes);
|
|
3278
|
+
const chunk = file.bytes.slice(start, end);
|
|
3279
|
+
const response = await this._fetchFn(part.url, { method: "PUT", body: bytesToBlob(chunk, file.mimeType) });
|
|
3280
|
+
if (!response.ok) throw new Error(`signed multipart PUT failed for part ${part.partNumber} (${response.status})`);
|
|
3281
|
+
const etag = response.headers.get("etag")?.replace(/^"|"$/g, "");
|
|
3282
|
+
if (!etag) throw new Error(`signed multipart PUT missing ETag for part ${part.partNumber}`);
|
|
3283
|
+
completedParts.push({ partNumber: part.partNumber, etag });
|
|
3284
|
+
uploaded += chunk.byteLength;
|
|
3285
|
+
onProgress?.(uploaded, file.sizeBytes);
|
|
2949
3286
|
}
|
|
3287
|
+
return completedParts;
|
|
3288
|
+
}
|
|
3289
|
+
async _uploadMultipart(file, options) {
|
|
3290
|
+
const { bytes, fileName, mimeType, sizeBytes, contentHash } = file;
|
|
2950
3291
|
const formData = new FormData();
|
|
2951
|
-
|
|
2952
|
-
new Uint8Array(ab).set(bytes);
|
|
2953
|
-
formData.append("file", new Blob([ab], { type: mimeType }), fileName);
|
|
3292
|
+
formData.append("file", bytesToBlob(bytes, mimeType), fileName);
|
|
2954
3293
|
formData.append("workspaceId", options.workspaceId);
|
|
2955
3294
|
if (options.kind) formData.append("kind", options.kind);
|
|
2956
3295
|
if (options.sourceAgentImUserId) formData.append("sourceAgentImUserId", options.sourceAgentImUserId);
|
|
2957
3296
|
if (options.sourceTaskId) formData.append("sourceTaskId", options.sourceTaskId);
|
|
2958
3297
|
if (options.metadata) formData.append("metadata", JSON.stringify(options.metadata));
|
|
3298
|
+
formData.append("contentSha256", contentHash);
|
|
2959
3299
|
const resp = await this._fetchFn(`${this._baseUrl}/api/im/assets`, {
|
|
2960
3300
|
method: "POST",
|
|
2961
3301
|
body: formData,
|
|
2962
|
-
headers:
|
|
3302
|
+
headers: {
|
|
3303
|
+
...this._getAuthHeaders(),
|
|
3304
|
+
"X-Content-Sha256": contentHash
|
|
3305
|
+
}
|
|
2963
3306
|
});
|
|
2964
|
-
options.onProgress?.(sizeBytes, sizeBytes);
|
|
2965
3307
|
const data = await resp.json().catch(() => ({}));
|
|
2966
3308
|
if (!resp.ok) {
|
|
2967
3309
|
return {
|
|
2968
3310
|
ok: false,
|
|
2969
|
-
error: data?.error || { code: "
|
|
3311
|
+
error: data?.error || { code: "http_error", message: `Upload failed (${resp.status})` }
|
|
2970
3312
|
};
|
|
2971
3313
|
}
|
|
3314
|
+
options.onProgress?.(sizeBytes, sizeBytes);
|
|
2972
3315
|
return data;
|
|
2973
3316
|
}
|
|
2974
3317
|
};
|
|
@@ -3088,11 +3431,11 @@ var FilesClient = class {
|
|
|
3088
3431
|
let bytes;
|
|
3089
3432
|
let fileName;
|
|
3090
3433
|
if (typeof input === "string") {
|
|
3091
|
-
const
|
|
3092
|
-
const
|
|
3093
|
-
const buf = await
|
|
3434
|
+
const fs5 = await import("fs");
|
|
3435
|
+
const path5 = await import("path");
|
|
3436
|
+
const buf = await fs5.promises.readFile(input);
|
|
3094
3437
|
bytes = new Uint8Array(buf);
|
|
3095
|
-
fileName = opts?.fileName ||
|
|
3438
|
+
fileName = opts?.fileName || path5.basename(input);
|
|
3096
3439
|
} else if (typeof Blob !== "undefined" && input instanceof Blob) {
|
|
3097
3440
|
const ab = await input.arrayBuffer();
|
|
3098
3441
|
bytes = new Uint8Array(ab);
|
|
@@ -3334,6 +3677,7 @@ var IMRealtimeClient = class {
|
|
|
3334
3677
|
};
|
|
3335
3678
|
var IMClient = class {
|
|
3336
3679
|
constructor(request2, wsBase, fetchFn, getAuthHeaders, offlineManager, communityHubConfig) {
|
|
3680
|
+
this._request = request2;
|
|
3337
3681
|
this.account = new AccountClient(request2);
|
|
3338
3682
|
this.direct = new DirectClient(request2);
|
|
3339
3683
|
this.groups = new GroupsClient(request2);
|
|
@@ -3360,7 +3704,7 @@ var IMClient = class {
|
|
|
3360
3704
|
}
|
|
3361
3705
|
/** IM health check */
|
|
3362
3706
|
async health() {
|
|
3363
|
-
return this.
|
|
3707
|
+
return this._request("GET", "/api/im/health");
|
|
3364
3708
|
}
|
|
3365
3709
|
/** Get workspace superset view with slot filtering */
|
|
3366
3710
|
async getWorkspace(scope, slots, includeContent) {
|
|
@@ -3368,7 +3712,22 @@ var IMClient = class {
|
|
|
3368
3712
|
if (scope) params.set("scope", scope);
|
|
3369
3713
|
if (slots?.length) params.set("slots", slots.join(","));
|
|
3370
3714
|
if (includeContent) params.set("includeContent", "true");
|
|
3371
|
-
return this.
|
|
3715
|
+
return this._request("GET", `/api/im/workspace/view?${params}`);
|
|
3716
|
+
}
|
|
3717
|
+
/**
|
|
3718
|
+
* Issue a typed IM API request via the shared `RequestFn` pipeline.
|
|
3719
|
+
*
|
|
3720
|
+
* Use this when you need to hit an IM endpoint that isn't (yet) exposed by a
|
|
3721
|
+
* sub-client (e.g. `/api/im/approvals`). Same auth + offline routing + retry
|
|
3722
|
+
* behaviour as the typed sub-clients.
|
|
3723
|
+
*
|
|
3724
|
+
* @example
|
|
3725
|
+
* const res = await client.im.request<ApprovalCreateResponse>(
|
|
3726
|
+
* 'POST', '/api/im/approvals', { category, title, context, options },
|
|
3727
|
+
* );
|
|
3728
|
+
*/
|
|
3729
|
+
async request(method, path5, body, query) {
|
|
3730
|
+
return this._request(method, path5, body, query);
|
|
3372
3731
|
}
|
|
3373
3732
|
};
|
|
3374
3733
|
var PrismerClient = class {
|
|
@@ -3412,20 +3771,20 @@ var PrismerClient = class {
|
|
|
3412
3771
|
let imRequest = this._offlineManager ? (m, p, b, q) => this._offlineManager.dispatch(m, p, b, q) : (m, p, b, q) => this._request(m, p, b, q);
|
|
3413
3772
|
if (config.identity) {
|
|
3414
3773
|
const baseRequest = imRequest;
|
|
3415
|
-
imRequest = (method,
|
|
3416
|
-
if (method === "POST" &&
|
|
3774
|
+
imRequest = (method, path5, body, query) => {
|
|
3775
|
+
if (method === "POST" && path5.includes("/messages") && body) {
|
|
3417
3776
|
const b = body;
|
|
3418
3777
|
if (!b.signature && !b.skipSigning) {
|
|
3419
3778
|
const ready = this._identityReady || Promise.resolve();
|
|
3420
3779
|
return ready.then(() => {
|
|
3421
3780
|
if (this._identity) {
|
|
3422
|
-
return this._signAndSend(baseRequest, method,
|
|
3781
|
+
return this._signAndSend(baseRequest, method, path5, b, query);
|
|
3423
3782
|
}
|
|
3424
|
-
return baseRequest(method,
|
|
3783
|
+
return baseRequest(method, path5, body, query);
|
|
3425
3784
|
});
|
|
3426
3785
|
}
|
|
3427
3786
|
}
|
|
3428
|
-
return baseRequest(method,
|
|
3787
|
+
return baseRequest(method, path5, body, query);
|
|
3429
3788
|
};
|
|
3430
3789
|
}
|
|
3431
3790
|
this.im = new IMClient(
|
|
@@ -3436,6 +3795,10 @@ var PrismerClient = class {
|
|
|
3436
3795
|
this._offlineManager,
|
|
3437
3796
|
config.community ?? null
|
|
3438
3797
|
);
|
|
3798
|
+
this.workspaces = this.im.workspaces;
|
|
3799
|
+
this.workspaceFiles = this.im.workspaceFiles;
|
|
3800
|
+
this.assets = this.im.assets;
|
|
3801
|
+
this.evolution = this.im.evolution;
|
|
3439
3802
|
}
|
|
3440
3803
|
/** Wait for identity to be ready (useful for tests or explicit await) */
|
|
3441
3804
|
async ensureIdentity() {
|
|
@@ -3443,9 +3806,9 @@ var PrismerClient = class {
|
|
|
3443
3806
|
return this._identity;
|
|
3444
3807
|
}
|
|
3445
3808
|
/** Auto-sign a message body and send (v1.8.0 S1) */
|
|
3446
|
-
async _signAndSend(baseRequest, method,
|
|
3809
|
+
async _signAndSend(baseRequest, method, path5, body, query) {
|
|
3447
3810
|
if (this._identityReady) await this._identityReady;
|
|
3448
|
-
if (!this._identity) return baseRequest(method,
|
|
3811
|
+
if (!this._identity) return baseRequest(method, path5, body, query);
|
|
3449
3812
|
const content = body.content || "";
|
|
3450
3813
|
const contentHashBytes = new Uint8Array(
|
|
3451
3814
|
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content))
|
|
@@ -3455,7 +3818,7 @@ var PrismerClient = class {
|
|
|
3455
3818
|
const payload = `1|${this._identity.did}|${body.type || "text"}|${timestamp}|${contentHash}`;
|
|
3456
3819
|
const payloadBytes = new TextEncoder().encode(payload);
|
|
3457
3820
|
const signature = await this._identity.sign(payloadBytes);
|
|
3458
|
-
return baseRequest(method,
|
|
3821
|
+
return baseRequest(method, path5, {
|
|
3459
3822
|
...body,
|
|
3460
3823
|
secVersion: 1,
|
|
3461
3824
|
senderDid: this._identity.did,
|
|
@@ -3484,14 +3847,47 @@ var PrismerClient = class {
|
|
|
3484
3847
|
await this._offlineManager.destroy();
|
|
3485
3848
|
}
|
|
3486
3849
|
}
|
|
3850
|
+
/**
|
|
3851
|
+
* Issue an authenticated raw HTTP request against the configured base URL,
|
|
3852
|
+
* returning the underlying `Response` so callers can inspect headers
|
|
3853
|
+
* (`Content-Range`, `Content-Length`, etc.) and stream the body.
|
|
3854
|
+
*
|
|
3855
|
+
* The path may be absolute (`/api/...`) or a full URL — full URLs are used
|
|
3856
|
+
* verbatim (useful for following 302 redirects), otherwise the path is
|
|
3857
|
+
* appended to the client's configured `baseUrl`. Authorization + `X-IM-Agent`
|
|
3858
|
+
* headers are added automatically; caller-supplied headers in `init.headers`
|
|
3859
|
+
* override them on collision.
|
|
3860
|
+
*
|
|
3861
|
+
* Use this for binary downloads / partial fetches; for normal JSON-envelope
|
|
3862
|
+
* IM requests use `client.im.request()` (typed) or the typed sub-clients.
|
|
3863
|
+
*/
|
|
3864
|
+
async fetchAuthed(url, init) {
|
|
3865
|
+
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.baseUrl}${url}`;
|
|
3866
|
+
const authHeaders = this._getAuthHeaders();
|
|
3867
|
+
const callerHeaders = {};
|
|
3868
|
+
if (init?.headers) {
|
|
3869
|
+
const h = init.headers;
|
|
3870
|
+
if (h instanceof Headers) {
|
|
3871
|
+
h.forEach((v, k) => {
|
|
3872
|
+
callerHeaders[k] = v;
|
|
3873
|
+
});
|
|
3874
|
+
} else if (Array.isArray(h)) {
|
|
3875
|
+
for (const [k, v] of h) callerHeaders[k] = v;
|
|
3876
|
+
} else {
|
|
3877
|
+
Object.assign(callerHeaders, h);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
const headers = { ...authHeaders, ...callerHeaders };
|
|
3881
|
+
return this.fetchFn(fullUrl, { ...init ?? {}, headers });
|
|
3882
|
+
}
|
|
3487
3883
|
// --------------------------------------------------------------------------
|
|
3488
3884
|
// Internal request helper
|
|
3489
3885
|
// --------------------------------------------------------------------------
|
|
3490
|
-
async _request(method,
|
|
3886
|
+
async _request(method, path5, body, query, _isRetry) {
|
|
3491
3887
|
const controller = new AbortController();
|
|
3492
3888
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3493
3889
|
try {
|
|
3494
|
-
let url = `${this.baseUrl}${
|
|
3890
|
+
let url = `${this.baseUrl}${path5}`;
|
|
3495
3891
|
if (query && Object.keys(query).length > 0) {
|
|
3496
3892
|
url += "?" + new URLSearchParams(query).toString();
|
|
3497
3893
|
}
|
|
@@ -3509,29 +3905,29 @@ var PrismerClient = class {
|
|
|
3509
3905
|
}
|
|
3510
3906
|
const response = await this.fetchFn(url, init);
|
|
3511
3907
|
const data = await response.json();
|
|
3512
|
-
if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !
|
|
3908
|
+
if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path5.includes("/token/refresh")) {
|
|
3513
3909
|
try {
|
|
3514
3910
|
const refreshRes = await this._request("POST", "/api/im/token/refresh", void 0, void 0, true);
|
|
3515
3911
|
if (refreshRes?.ok && refreshRes?.data?.token) {
|
|
3516
3912
|
this.apiKey = refreshRes.data.token;
|
|
3517
|
-
return this._request(method,
|
|
3913
|
+
return this._request(method, path5, body, query, true);
|
|
3518
3914
|
}
|
|
3519
3915
|
} catch {
|
|
3520
3916
|
}
|
|
3521
3917
|
}
|
|
3522
3918
|
if (!response.ok) {
|
|
3523
|
-
const err = data.error || { code: "
|
|
3919
|
+
const err = data.error || { code: "http_error", message: `Request failed with status ${response.status}` };
|
|
3524
3920
|
return { ...data, success: false, ok: false, error: err };
|
|
3525
3921
|
}
|
|
3526
3922
|
return data;
|
|
3527
|
-
} catch (
|
|
3528
|
-
if (
|
|
3529
|
-
return { success: false, ok: false, error: { code: "
|
|
3923
|
+
} catch (error) {
|
|
3924
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3925
|
+
return { success: false, ok: false, error: { code: "timeout", message: "Request timed out" } };
|
|
3530
3926
|
}
|
|
3531
3927
|
return {
|
|
3532
3928
|
success: false,
|
|
3533
3929
|
ok: false,
|
|
3534
|
-
error: { code: "
|
|
3930
|
+
error: { code: "cloud_unreachable", message: error instanceof Error ? error.message : "Unknown error" }
|
|
3535
3931
|
};
|
|
3536
3932
|
} finally {
|
|
3537
3933
|
clearTimeout(timeoutId);
|
|
@@ -3601,166 +3997,554 @@ var PrismerClient = class {
|
|
|
3601
3997
|
}
|
|
3602
3998
|
};
|
|
3603
3999
|
|
|
3604
|
-
// src/ui.ts
|
|
3605
|
-
var pc = __toESM(require("picocolors"));
|
|
3606
|
-
var clack = __toESM(require("@clack/prompts"));
|
|
4000
|
+
// src/cli-ui.ts
|
|
3607
4001
|
var fs = __toESM(require("fs"));
|
|
3608
4002
|
var path = __toESM(require("path"));
|
|
3609
|
-
var
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
4003
|
+
var import_node_url = require("url");
|
|
4004
|
+
var import_meta = {};
|
|
4005
|
+
var BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4006
|
+
var COMPACT_BANNER = ["\u25C7 PRISMER", " Cloud CLI"];
|
|
4007
|
+
function thisDirname() {
|
|
4008
|
+
if (typeof __dirname !== "undefined") return __dirname;
|
|
3613
4009
|
try {
|
|
3614
|
-
|
|
3615
|
-
if (
|
|
3616
|
-
iconPath = path.resolve(__dirname, "..", "..", "icon");
|
|
3617
|
-
}
|
|
3618
|
-
if (!fs.existsSync(iconPath)) return;
|
|
3619
|
-
const raw = fs.readFileSync(iconPath, "utf-8");
|
|
3620
|
-
const lines = raw.split("\n");
|
|
3621
|
-
const termWidth = process.stdout.columns || 80;
|
|
3622
|
-
const colorized = lines.map((line) => {
|
|
3623
|
-
const trimmed = line.trimEnd();
|
|
3624
|
-
if (!trimmed) return "";
|
|
3625
|
-
let result = "";
|
|
3626
|
-
let visibleLen = 0;
|
|
3627
|
-
for (const ch of trimmed) {
|
|
3628
|
-
if (visibleLen >= termWidth - 1) break;
|
|
3629
|
-
if (ch === "\u2592") {
|
|
3630
|
-
result += pc.cyan(ch);
|
|
3631
|
-
} else if (ch === "\u2593") {
|
|
3632
|
-
result += pc.white(ch);
|
|
3633
|
-
} else if (ch === "\u2588") {
|
|
3634
|
-
result += pc.white(ch);
|
|
3635
|
-
} else {
|
|
3636
|
-
result += ch;
|
|
3637
|
-
}
|
|
3638
|
-
visibleLen++;
|
|
3639
|
-
}
|
|
3640
|
-
return result;
|
|
3641
|
-
});
|
|
3642
|
-
while (colorized.length > 0 && colorized[colorized.length - 1].trim() === "") {
|
|
3643
|
-
colorized.pop();
|
|
3644
|
-
}
|
|
3645
|
-
console.log(colorized.join("\n"));
|
|
3646
|
-
console.log();
|
|
4010
|
+
const metaUrl = typeof import_meta !== "undefined" ? import_meta.url : void 0;
|
|
4011
|
+
if (metaUrl) return path.dirname((0, import_node_url.fileURLToPath)(metaUrl));
|
|
3647
4012
|
} catch {
|
|
3648
4013
|
}
|
|
4014
|
+
return process.cwd();
|
|
3649
4015
|
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
}
|
|
3672
|
-
|
|
3673
|
-
|
|
4016
|
+
function findIconPath(size = "big") {
|
|
4017
|
+
const name = size === "big" ? "icon" : "smallicon";
|
|
4018
|
+
const here = thisDirname();
|
|
4019
|
+
const candidates = [
|
|
4020
|
+
// npm-installed: node_modules/@prismer/sdk/dist/cli.js → ../icon (SDK ships at pkg root)
|
|
4021
|
+
path.resolve(here, "..", name),
|
|
4022
|
+
// alternate dist layout (sub-bundle): dist/bin/cli.js → ../../icon
|
|
4023
|
+
path.resolve(here, "..", "..", name),
|
|
4024
|
+
// source/typecheck: src/cli-ui.ts → ../icon
|
|
4025
|
+
path.resolve(here, "..", name),
|
|
4026
|
+
// dev mode: cwd happens to be sdk root
|
|
4027
|
+
path.resolve(process.cwd(), name),
|
|
4028
|
+
path.resolve(process.cwd(), "sdk/prismer-cloud/typescript", name),
|
|
4029
|
+
// legacy assets/ layout (runtime parity, in case SDK ever adopts it)
|
|
4030
|
+
path.resolve(here, "..", "assets", name),
|
|
4031
|
+
path.resolve(here, "..", "..", "assets", name)
|
|
4032
|
+
];
|
|
4033
|
+
for (const candidate of candidates) {
|
|
4034
|
+
try {
|
|
4035
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
4036
|
+
} catch {
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
return null;
|
|
3674
4040
|
}
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
4041
|
+
var UI = class {
|
|
4042
|
+
constructor(opts) {
|
|
4043
|
+
this.mode = opts?.mode ?? "pretty";
|
|
4044
|
+
this.stream = opts?.stream ?? process.stdout;
|
|
4045
|
+
this.errStream = opts?.errStream ?? process.stderr;
|
|
4046
|
+
if (opts?.color !== void 0) {
|
|
4047
|
+
this.colorEnabled = opts.color;
|
|
4048
|
+
} else {
|
|
4049
|
+
const isTTY = this.stream.isTTY === true;
|
|
4050
|
+
const noColor = Boolean(process.env["NO_COLOR"]);
|
|
4051
|
+
this.colorEnabled = isTTY && !noColor;
|
|
4052
|
+
}
|
|
3678
4053
|
}
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
s.stop(pc.green(`${SYMBOLS.success} ${message}`));
|
|
3684
|
-
return result;
|
|
3685
|
-
} catch (err) {
|
|
3686
|
-
s.stop(pc.red(`${SYMBOLS.error} ${message}`));
|
|
3687
|
-
throw err;
|
|
4054
|
+
// ---- Internal color helpers ----
|
|
4055
|
+
ansi(open, close, text) {
|
|
4056
|
+
if (!this.colorEnabled) return text;
|
|
4057
|
+
return `\x1B[${open}m${text}\x1B[${close}m`;
|
|
3688
4058
|
}
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
if (headers.length === 0) return;
|
|
3692
|
-
const widths = headers.map((h, i) => {
|
|
3693
|
-
const dataMax = rows.reduce((max, row) => Math.max(max, (row[i] || "").length), 0);
|
|
3694
|
-
return Math.max(h.length, dataMax);
|
|
3695
|
-
});
|
|
3696
|
-
const PAD = 2;
|
|
3697
|
-
const headerLine = headers.map((h, i) => h.padEnd(widths[i] + PAD)).join("");
|
|
3698
|
-
if (isTTY) {
|
|
3699
|
-
console.log(pc.bold(headerLine));
|
|
3700
|
-
const separator = widths.map((w) => "\u2500".repeat(w)).join(" ");
|
|
3701
|
-
console.log(pc.dim(separator));
|
|
3702
|
-
} else {
|
|
3703
|
-
console.log(headerLine);
|
|
3704
|
-
const separator = widths.map((w) => "-".repeat(w)).join(" ");
|
|
3705
|
-
console.log(separator);
|
|
4059
|
+
green(t) {
|
|
4060
|
+
return this.ansi(32, 39, t);
|
|
3706
4061
|
}
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
console.log(line);
|
|
4062
|
+
red(t) {
|
|
4063
|
+
return this.ansi(31, 39, t);
|
|
3710
4064
|
}
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
const keys = Object.keys(pairs);
|
|
3714
|
-
if (keys.length === 0) return;
|
|
3715
|
-
const maxKeyLen = keys.reduce((max, k) => Math.max(max, k.length), 0);
|
|
3716
|
-
for (const key of keys) {
|
|
3717
|
-
const label = isTTY ? pc.bold(key.padEnd(maxKeyLen)) : key.padEnd(maxKeyLen);
|
|
3718
|
-
const value = pairs[key];
|
|
3719
|
-
console.log(` ${label} ${value}`);
|
|
4065
|
+
yellow(t) {
|
|
4066
|
+
return this.ansi(33, 39, t);
|
|
3720
4067
|
}
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
4068
|
+
cyan(t) {
|
|
4069
|
+
return this.ansi(36, 39, t);
|
|
4070
|
+
}
|
|
4071
|
+
dim(t) {
|
|
4072
|
+
return this.ansi(2, 22, t);
|
|
4073
|
+
}
|
|
4074
|
+
bold(t) {
|
|
4075
|
+
return this.ansi(1, 22, t);
|
|
4076
|
+
}
|
|
4077
|
+
gray(t) {
|
|
4078
|
+
return this.ansi(90, 39, t);
|
|
4079
|
+
}
|
|
4080
|
+
brandMark() {
|
|
4081
|
+
return this.cyan("\u25C7");
|
|
4082
|
+
}
|
|
4083
|
+
colorBrandLine(line) {
|
|
4084
|
+
let out = "";
|
|
4085
|
+
for (const ch of line) {
|
|
4086
|
+
if (ch === "\u2592") {
|
|
4087
|
+
out += this.cyan(ch);
|
|
4088
|
+
} else if (ch === "\u2593") {
|
|
4089
|
+
out += this.dim(ch);
|
|
4090
|
+
} else {
|
|
4091
|
+
out += ch;
|
|
3738
4092
|
}
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
4093
|
+
}
|
|
4094
|
+
return out;
|
|
4095
|
+
}
|
|
4096
|
+
// ---- Core write helpers ----
|
|
4097
|
+
write(text) {
|
|
4098
|
+
this.stream.write(text);
|
|
4099
|
+
}
|
|
4100
|
+
writeErr(text) {
|
|
4101
|
+
this.errStream.write(text);
|
|
4102
|
+
}
|
|
4103
|
+
// ---- Level 1: Header ----
|
|
4104
|
+
header(text) {
|
|
4105
|
+
if (this.mode === "json") return;
|
|
4106
|
+
const prefix = text.startsWith("Prismer") ? this.brandMark() + " " : "";
|
|
4107
|
+
this.write(prefix + this.bold(text) + "\n");
|
|
4108
|
+
}
|
|
4109
|
+
smallHeader(subtitle) {
|
|
4110
|
+
if (this.mode === "json" || this.mode === "quiet") return;
|
|
4111
|
+
const iconPath = findIconPath("small");
|
|
4112
|
+
if (iconPath !== null) {
|
|
4113
|
+
try {
|
|
4114
|
+
const raw = fs.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
|
|
4115
|
+
for (const line of raw.split("\n")) {
|
|
4116
|
+
this.write(this.cyan(line) + "\n");
|
|
4117
|
+
}
|
|
4118
|
+
} catch {
|
|
4119
|
+
this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
|
|
3742
4120
|
}
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
} catch (err) {
|
|
3746
|
-
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
3747
|
-
`);
|
|
3748
|
-
process.exit(1);
|
|
4121
|
+
} else {
|
|
4122
|
+
this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
|
|
3749
4123
|
}
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
4124
|
+
if (subtitle !== void 0 && subtitle.length > 0) {
|
|
4125
|
+
this.write(this.dim(" " + subtitle) + "\n");
|
|
4126
|
+
}
|
|
4127
|
+
this.blank();
|
|
4128
|
+
}
|
|
4129
|
+
banner(subtitle, opts) {
|
|
4130
|
+
if (this.mode === "json" || this.mode === "quiet") return;
|
|
4131
|
+
const envColumns = process.env["COLUMNS"] !== void 0 ? parseInt(process.env["COLUMNS"], 10) : NaN;
|
|
4132
|
+
const width = this.stream.columns ?? process.stdout.columns ?? (Number.isFinite(envColumns) ? envColumns : 80);
|
|
4133
|
+
const iconPath = findIconPath("big");
|
|
4134
|
+
const shouldUseFull = opts?.full === true || width >= 120;
|
|
4135
|
+
if (shouldUseFull && iconPath !== null) {
|
|
4136
|
+
try {
|
|
4137
|
+
const raw = fs.readFileSync(iconPath, "utf-8");
|
|
4138
|
+
const lines = raw.split("\n");
|
|
4139
|
+
for (const line of lines) {
|
|
4140
|
+
const stripped = line.trimEnd();
|
|
4141
|
+
if (stripped.length === 0) {
|
|
4142
|
+
this.write("\n");
|
|
4143
|
+
continue;
|
|
4144
|
+
}
|
|
4145
|
+
const clipped = stripped.length >= width ? stripped.slice(0, Math.max(width - 1, 1)) : stripped;
|
|
4146
|
+
this.write(this.colorBrandLine(clipped) + "\n");
|
|
4147
|
+
}
|
|
4148
|
+
} catch {
|
|
4149
|
+
this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
|
|
4150
|
+
this.write(this.dim(COMPACT_BANNER[1] ?? " Cloud CLI") + "\n");
|
|
3759
4151
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
4152
|
+
} else {
|
|
4153
|
+
this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
|
|
4154
|
+
this.write(this.dim(COMPACT_BANNER[1] ?? " Cloud CLI") + "\n");
|
|
4155
|
+
}
|
|
4156
|
+
if (subtitle !== void 0 && subtitle.length > 0) {
|
|
4157
|
+
this.write(this.dim(" " + subtitle) + "\n");
|
|
4158
|
+
}
|
|
4159
|
+
this.blank();
|
|
4160
|
+
}
|
|
4161
|
+
// ---- Level 2: Primary data ----
|
|
4162
|
+
blank() {
|
|
4163
|
+
if (this.mode === "json") return;
|
|
4164
|
+
this.write("\n");
|
|
4165
|
+
}
|
|
4166
|
+
line(text) {
|
|
4167
|
+
if (this.mode === "json") return;
|
|
4168
|
+
this.write(text + "\n");
|
|
4169
|
+
}
|
|
4170
|
+
info(text) {
|
|
4171
|
+
this.line(text);
|
|
4172
|
+
}
|
|
4173
|
+
// ---- Level 3: Secondary ----
|
|
4174
|
+
secondary(text, indent = 2) {
|
|
4175
|
+
if (this.mode === "json") return;
|
|
4176
|
+
this.write(" ".repeat(indent) + this.dim(text) + "\n");
|
|
4177
|
+
}
|
|
4178
|
+
// ---- Level 4: Action tips ----
|
|
4179
|
+
tip(text) {
|
|
4180
|
+
if (this.mode === "json") return;
|
|
4181
|
+
this.write(this.cyan("Tip:") + " " + text + "\n");
|
|
4182
|
+
}
|
|
4183
|
+
next(text) {
|
|
4184
|
+
if (this.mode === "json") return;
|
|
4185
|
+
this.write(this.cyan("Next:") + " " + text + "\n");
|
|
4186
|
+
}
|
|
4187
|
+
// ---- Level 5: Status indicators ----
|
|
4188
|
+
ok(text, detail) {
|
|
4189
|
+
if (this.mode === "json") return;
|
|
4190
|
+
const suffix = detail ? " " + this.dim(detail) : "";
|
|
4191
|
+
this.write(" " + this.green("\u2713") + " " + text + suffix + "\n");
|
|
4192
|
+
}
|
|
4193
|
+
success(text, detail) {
|
|
4194
|
+
this.ok(text, detail);
|
|
4195
|
+
}
|
|
4196
|
+
fail(text, detail) {
|
|
4197
|
+
if (this.mode === "json") return;
|
|
4198
|
+
const suffix = detail ? " " + this.dim(detail) : "";
|
|
4199
|
+
this.write(" " + this.red("\u2717") + " " + text + suffix + "\n");
|
|
4200
|
+
}
|
|
4201
|
+
online(text) {
|
|
4202
|
+
if (this.mode === "json") return;
|
|
4203
|
+
this.write(" " + this.green("\u25CF") + " " + text + "\n");
|
|
4204
|
+
}
|
|
4205
|
+
offline(text) {
|
|
4206
|
+
if (this.mode === "json") return;
|
|
4207
|
+
this.write(" " + this.gray("\u25CB") + " " + text + "\n");
|
|
4208
|
+
}
|
|
4209
|
+
notInstalled(text) {
|
|
4210
|
+
if (this.mode === "json") return;
|
|
4211
|
+
this.write(" " + this.dim("\xB7") + " " + this.dim(text) + "\n");
|
|
4212
|
+
}
|
|
4213
|
+
pending(text) {
|
|
4214
|
+
if (this.mode === "json") return;
|
|
4215
|
+
this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
|
|
4216
|
+
}
|
|
4217
|
+
warn(text, detail) {
|
|
4218
|
+
if (this.mode === "json") return;
|
|
4219
|
+
const suffix = detail ? " " + this.dim(detail) : "";
|
|
4220
|
+
this.write(" " + this.yellow("!") + " " + text + suffix + "\n");
|
|
4221
|
+
}
|
|
4222
|
+
// ---- Level 6: Error block ----
|
|
4223
|
+
error(what, cause, fix) {
|
|
4224
|
+
if (this.mode === "json") return;
|
|
4225
|
+
this.writeErr(this.red("\u2717") + " " + what + "\n");
|
|
4226
|
+
if (cause !== void 0) {
|
|
4227
|
+
this.writeErr(" " + this.dim("Cause:") + " " + this.dim(cause) + "\n");
|
|
4228
|
+
}
|
|
4229
|
+
if (fix !== void 0) {
|
|
4230
|
+
this.writeErr(" " + this.cyan("Fix:") + " " + fix + "\n");
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
table(rowsOrOpts, maybeOpts) {
|
|
4234
|
+
if (this.mode === "json") return;
|
|
4235
|
+
const rows = Array.isArray(rowsOrOpts) ? rowsOrOpts : rowsOrOpts.rows;
|
|
4236
|
+
const opts = Array.isArray(rowsOrOpts) ? maybeOpts : { columns: rowsOrOpts.columns, maxWidth: rowsOrOpts.maxWidth };
|
|
4237
|
+
if (!opts) throw new Error("table() requires columns");
|
|
4238
|
+
const maxWidth = opts.maxWidth ?? (process.stdout.columns || 80);
|
|
4239
|
+
const cols = opts.columns;
|
|
4240
|
+
const widths = cols.map((col) => col.length);
|
|
4241
|
+
for (const row of rows) {
|
|
4242
|
+
cols.forEach((col, i) => {
|
|
4243
|
+
const val = row[col] ?? "";
|
|
4244
|
+
const w = widths[i] ?? 0;
|
|
4245
|
+
if (val.length > w) widths[i] = val.length;
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
const totalWidth = widths.reduce((a, b) => a + b, 0) + (cols.length - 1) * 2 + 2;
|
|
4249
|
+
if (totalWidth > maxWidth) {
|
|
4250
|
+
for (let i = 0; i < rows.length; i++) {
|
|
4251
|
+
const row = rows[i];
|
|
4252
|
+
if (!row) continue;
|
|
4253
|
+
for (const col of cols) {
|
|
4254
|
+
const val = row[col] ?? "";
|
|
4255
|
+
this.write(" " + this.bold(col + ":") + " " + val + "\n");
|
|
4256
|
+
}
|
|
4257
|
+
if (i < rows.length - 1) this.write("\n");
|
|
4258
|
+
}
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
const header = cols.map((col, i) => col.toUpperCase().padEnd(widths[i] ?? col.length)).join(" ");
|
|
4262
|
+
this.write(" " + this.dim(header) + "\n");
|
|
4263
|
+
for (const row of rows) {
|
|
4264
|
+
const line = cols.map((col, i) => (row[col] ?? "").padEnd(widths[i] ?? col.length)).join(" ");
|
|
4265
|
+
this.write(" " + line + "\n");
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
// ---- Spinner ----
|
|
4269
|
+
spinner(text) {
|
|
4270
|
+
if (this.mode === "quiet" || this.mode === "json") {
|
|
4271
|
+
return {
|
|
4272
|
+
update() {
|
|
4273
|
+
},
|
|
4274
|
+
stop() {
|
|
4275
|
+
}
|
|
4276
|
+
};
|
|
4277
|
+
}
|
|
4278
|
+
const isTTY = this.stream.isTTY === true;
|
|
4279
|
+
if (!isTTY || !this.colorEnabled) {
|
|
4280
|
+
this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
|
|
4281
|
+
return {
|
|
4282
|
+
update: (t) => {
|
|
4283
|
+
this.write(" " + this.yellow("\u27F3") + " " + t + "\n");
|
|
4284
|
+
},
|
|
4285
|
+
stop: (final) => {
|
|
4286
|
+
if (final) this.write(" " + this.green("\u2713") + " " + final + "\n");
|
|
4287
|
+
}
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
let current = text;
|
|
4291
|
+
let frameIdx = 0;
|
|
4292
|
+
let stopped = false;
|
|
4293
|
+
const write = this.write.bind(this);
|
|
4294
|
+
const colorFn = this.yellow.bind(this);
|
|
4295
|
+
const greenFn = this.green.bind(this);
|
|
4296
|
+
function renderFrame() {
|
|
4297
|
+
const frame = BRAILLE_FRAMES[frameIdx % BRAILLE_FRAMES.length] ?? "\u280B";
|
|
4298
|
+
const line = " " + colorFn(frame) + " " + current;
|
|
4299
|
+
write("\r" + line);
|
|
4300
|
+
frameIdx++;
|
|
4301
|
+
}
|
|
4302
|
+
renderFrame();
|
|
4303
|
+
const timer = setInterval(renderFrame, 80);
|
|
4304
|
+
return {
|
|
4305
|
+
update(t) {
|
|
4306
|
+
if (stopped) return;
|
|
4307
|
+
current = t;
|
|
4308
|
+
},
|
|
4309
|
+
stop(final) {
|
|
4310
|
+
if (stopped) return;
|
|
4311
|
+
stopped = true;
|
|
4312
|
+
clearInterval(timer);
|
|
4313
|
+
write("\r\x1B[2K");
|
|
4314
|
+
if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
|
|
4315
|
+
}
|
|
4316
|
+
};
|
|
4317
|
+
}
|
|
4318
|
+
// ---- Progress bar ----
|
|
4319
|
+
progress(text, total) {
|
|
4320
|
+
if (this.mode === "quiet" || this.mode === "json") {
|
|
4321
|
+
return {
|
|
4322
|
+
update() {
|
|
4323
|
+
},
|
|
4324
|
+
stop() {
|
|
4325
|
+
}
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
const isTTY = this.stream.isTTY === true;
|
|
4329
|
+
const start = Date.now();
|
|
4330
|
+
const write = this.write.bind(this);
|
|
4331
|
+
const colorFn = this.cyan.bind(this);
|
|
4332
|
+
const dimFn = this.dim.bind(this);
|
|
4333
|
+
const greenFn = this.green.bind(this);
|
|
4334
|
+
let last = 0;
|
|
4335
|
+
let lastDetail = "";
|
|
4336
|
+
let stopped = false;
|
|
4337
|
+
const render = () => {
|
|
4338
|
+
if (stopped) return;
|
|
4339
|
+
const frac = total > 0 ? Math.min(1, Math.max(0, last / total)) : 0;
|
|
4340
|
+
const pct = Math.floor(frac * 100);
|
|
4341
|
+
const width = 20;
|
|
4342
|
+
const filled = Math.floor(frac * width);
|
|
4343
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
4344
|
+
const elapsed = (Date.now() - start) / 1e3;
|
|
4345
|
+
const eta = frac > 0.01 ? Math.max(0, elapsed / frac - elapsed) : 0;
|
|
4346
|
+
const etaStr = frac >= 1 ? "" : ` \xB7 ${eta < 1 ? "<1s" : Math.round(eta) + "s"} left`;
|
|
4347
|
+
const detailStr = lastDetail ? ` \xB7 ${lastDetail}` : "";
|
|
4348
|
+
const line = ` ${text} [${colorFn(bar)}] ${String(pct).padStart(3)}%${detailStr}${dimFn(etaStr)}`;
|
|
4349
|
+
if (isTTY && this.colorEnabled) {
|
|
4350
|
+
write("\r\x1B[2K" + line);
|
|
4351
|
+
} else {
|
|
4352
|
+
write(line + "\n");
|
|
4353
|
+
}
|
|
4354
|
+
};
|
|
4355
|
+
render();
|
|
4356
|
+
return {
|
|
4357
|
+
update: (current, detail) => {
|
|
4358
|
+
if (stopped) return;
|
|
4359
|
+
last = current;
|
|
4360
|
+
if (detail !== void 0) lastDetail = detail;
|
|
4361
|
+
render();
|
|
4362
|
+
},
|
|
4363
|
+
stop: (final) => {
|
|
4364
|
+
if (stopped) return;
|
|
4365
|
+
stopped = true;
|
|
4366
|
+
if (isTTY && this.colorEnabled) write("\r\x1B[2K");
|
|
4367
|
+
if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
|
|
4368
|
+
}
|
|
4369
|
+
};
|
|
4370
|
+
}
|
|
4371
|
+
// ---- JSON output ----
|
|
4372
|
+
json(payload, opts) {
|
|
4373
|
+
const indent = opts?.pretty ? 2 : void 0;
|
|
4374
|
+
this.write(JSON.stringify(payload, null, indent) + "\n");
|
|
4375
|
+
}
|
|
4376
|
+
result(pretty, jsonPayload) {
|
|
4377
|
+
if (this.mode === "pretty") {
|
|
4378
|
+
pretty();
|
|
4379
|
+
} else {
|
|
4380
|
+
this.json(jsonPayload);
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
};
|
|
4384
|
+
var _ui = null;
|
|
4385
|
+
function getUI() {
|
|
4386
|
+
if (!_ui) _ui = new UI();
|
|
4387
|
+
return _ui;
|
|
4388
|
+
}
|
|
4389
|
+
function setUI(ui) {
|
|
4390
|
+
_ui = ui;
|
|
4391
|
+
}
|
|
4392
|
+
function applyCommonFlags(argv) {
|
|
4393
|
+
let mode = "pretty";
|
|
4394
|
+
const isTTY = process.stdout.isTTY === true;
|
|
4395
|
+
const noColorEnv = Boolean(process.env["NO_COLOR"]);
|
|
4396
|
+
let color = isTTY && !noColorEnv;
|
|
4397
|
+
const rest = [];
|
|
4398
|
+
for (const arg of argv) {
|
|
4399
|
+
switch (arg) {
|
|
4400
|
+
case "--no-color":
|
|
4401
|
+
color = false;
|
|
4402
|
+
break;
|
|
4403
|
+
case "--color":
|
|
4404
|
+
color = true;
|
|
4405
|
+
break;
|
|
4406
|
+
case "--json":
|
|
4407
|
+
case "--pretty-json":
|
|
4408
|
+
mode = "json";
|
|
4409
|
+
if (arg === "--json") rest.push(arg);
|
|
4410
|
+
break;
|
|
4411
|
+
case "--quiet":
|
|
4412
|
+
mode = "quiet";
|
|
4413
|
+
break;
|
|
4414
|
+
default:
|
|
4415
|
+
rest.push(arg);
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
return { mode, color, restArgv: rest };
|
|
4419
|
+
}
|
|
4420
|
+
function displayBanner(subtitle) {
|
|
4421
|
+
getUI().banner(subtitle ?? "Cloud CLI v2.0.0", { full: true });
|
|
4422
|
+
}
|
|
4423
|
+
function success(msg) {
|
|
4424
|
+
getUI().ok(msg);
|
|
4425
|
+
}
|
|
4426
|
+
function warn(msg) {
|
|
4427
|
+
getUI().warn(msg);
|
|
4428
|
+
}
|
|
4429
|
+
function info(msg) {
|
|
4430
|
+
getUI().info(msg);
|
|
4431
|
+
}
|
|
4432
|
+
function dim(msg) {
|
|
4433
|
+
getUI().secondary(msg);
|
|
4434
|
+
}
|
|
4435
|
+
function errorLine(msg) {
|
|
4436
|
+
const ui = getUI();
|
|
4437
|
+
if (ui.mode === "json") {
|
|
4438
|
+
ui.json({ ok: false, error: { code: "cli_error", message: msg } }, { pretty: true });
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
4441
|
+
ui.writeErr("Error: " + msg + "\n");
|
|
4442
|
+
}
|
|
4443
|
+
function keyValue(pairs) {
|
|
4444
|
+
const ui = getUI();
|
|
4445
|
+
if (ui.mode === "json") {
|
|
4446
|
+
ui.json(pairs);
|
|
4447
|
+
return;
|
|
4448
|
+
}
|
|
4449
|
+
const keys = Object.keys(pairs);
|
|
4450
|
+
if (keys.length === 0) return;
|
|
4451
|
+
const maxKeyLen = keys.reduce((max, k) => Math.max(max, k.length), 0);
|
|
4452
|
+
for (const key of keys) {
|
|
4453
|
+
const label = key.padEnd(maxKeyLen);
|
|
4454
|
+
const value = pairs[key] ?? "";
|
|
4455
|
+
ui.line(" " + label + " " + value);
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
function table(headers, rows) {
|
|
4459
|
+
const objectRows = rows.map((r) => {
|
|
4460
|
+
const obj = {};
|
|
4461
|
+
headers.forEach((h, i) => {
|
|
4462
|
+
obj[h] = r[i] ?? "";
|
|
4463
|
+
});
|
|
4464
|
+
return obj;
|
|
4465
|
+
});
|
|
4466
|
+
getUI().table(objectRows, { columns: headers });
|
|
4467
|
+
}
|
|
4468
|
+
async function withSpinner(message, fn) {
|
|
4469
|
+
const sp = getUI().spinner(message);
|
|
4470
|
+
try {
|
|
4471
|
+
const result = await fn();
|
|
4472
|
+
sp.stop(message);
|
|
4473
|
+
return result;
|
|
4474
|
+
} catch (err) {
|
|
4475
|
+
sp.stop();
|
|
4476
|
+
throw err;
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
// src/commands/im.ts
|
|
4481
|
+
function register(parent, getIMClient2, _getAPIClient) {
|
|
4482
|
+
const im = parent.command("im").description("IM messaging, groups, conversations, and credits");
|
|
4483
|
+
im.command("send <user-id-or-username> <message>").description("Send a direct message to a user (id by default, --by-username to resolve)").option("-t, --type <type>", "Message type: text, markdown, code, file, etc.", "text").option("--reply-to <msg-id>", "Reply to a specific message ID (parentId)").option("--conversation-id <id>", "Pin message to a specific conversation/session").option("--asset-id <id>", "Attach a previously uploaded asset (treats type as file)").option("--by-username", "Treat the first arg as a username; resolve to imUserId first").option("--json", "Output raw JSON response").action(async (target, message, opts) => {
|
|
4484
|
+
const client = getIMClient2();
|
|
4485
|
+
try {
|
|
4486
|
+
let userId = target;
|
|
4487
|
+
if (opts.byUsername) {
|
|
4488
|
+
const discoverRes = await client.im.contacts.discover();
|
|
4489
|
+
if (!discoverRes.ok || !Array.isArray(discoverRes.data)) {
|
|
4490
|
+
process.stderr.write(`Error: could not resolve username "${target}" \u2014 discover failed.
|
|
4491
|
+
`);
|
|
4492
|
+
process.exit(1);
|
|
4493
|
+
}
|
|
4494
|
+
const needle = target.trim().toLowerCase().replace(/^@/, "");
|
|
4495
|
+
const match = discoverRes.data.find((u) => {
|
|
4496
|
+
const vals = [u.username, u.displayName, u.userId].map(
|
|
4497
|
+
(v) => typeof v === "string" ? v.trim().toLowerCase() : ""
|
|
4498
|
+
);
|
|
4499
|
+
return vals.includes(needle);
|
|
4500
|
+
});
|
|
4501
|
+
if (!match?.userId || typeof match.userId !== "string") {
|
|
4502
|
+
process.stderr.write(`Error: could not resolve username "${target}" to an IM user.
|
|
4503
|
+
`);
|
|
4504
|
+
process.exit(1);
|
|
4505
|
+
}
|
|
4506
|
+
userId = match.userId;
|
|
4507
|
+
}
|
|
4508
|
+
let effectiveType = opts.type;
|
|
4509
|
+
if (opts.assetId && (!effectiveType || effectiveType === "text")) effectiveType = "file";
|
|
4510
|
+
const sendOpts = {
|
|
4511
|
+
type: effectiveType
|
|
4512
|
+
};
|
|
4513
|
+
if (opts.replyTo) sendOpts.parentId = opts.replyTo;
|
|
4514
|
+
if (opts.assetId) {
|
|
4515
|
+
sendOpts.attachments = [{ kind: "asset", assetId: opts.assetId, role: "attachment" }];
|
|
4516
|
+
}
|
|
4517
|
+
const res = opts.conversationId ? await client.im.messages.send(opts.conversationId, message, sendOpts) : await client.im.direct.send(userId, message, sendOpts);
|
|
4518
|
+
if (!res.ok) {
|
|
4519
|
+
process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
|
|
4520
|
+
`);
|
|
4521
|
+
process.exit(1);
|
|
4522
|
+
}
|
|
4523
|
+
if (opts.json) {
|
|
4524
|
+
process.stdout.write(JSON.stringify(res.data, null, 2) + "\n");
|
|
4525
|
+
return;
|
|
4526
|
+
}
|
|
4527
|
+
process.stdout.write(`Message sent (conversationId: ${res.data?.conversationId})
|
|
4528
|
+
`);
|
|
4529
|
+
} catch (err) {
|
|
4530
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
4531
|
+
`);
|
|
4532
|
+
process.exit(1);
|
|
4533
|
+
}
|
|
4534
|
+
});
|
|
4535
|
+
im.command("messages <user-id>").description("View direct message history with a user").option("-n, --limit <n>", "Max number of messages to fetch", "20").option("--json", "Output raw JSON response").action(async (userId, opts) => {
|
|
4536
|
+
const client = getIMClient2();
|
|
4537
|
+
try {
|
|
4538
|
+
const res = await client.im.direct.getMessages(userId, { limit: parseInt(opts.limit, 10) });
|
|
4539
|
+
if (!res.ok) {
|
|
4540
|
+
process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
|
|
4541
|
+
`);
|
|
4542
|
+
process.exit(1);
|
|
4543
|
+
}
|
|
4544
|
+
const msgs = res.data || [];
|
|
4545
|
+
if (opts.json) {
|
|
4546
|
+
process.stdout.write(JSON.stringify(msgs, null, 2) + "\n");
|
|
4547
|
+
return;
|
|
3764
4548
|
}
|
|
3765
4549
|
if (msgs.length === 0) {
|
|
3766
4550
|
process.stdout.write("No messages.\n");
|
|
@@ -3819,12 +4603,16 @@ function register(parent, getIMClient2, _getAPIClient) {
|
|
|
3819
4603
|
process.exit(1);
|
|
3820
4604
|
}
|
|
3821
4605
|
});
|
|
3822
|
-
im.command("discover").description("Discover available agents").option("--type <type>", "Filter by agent type").option("--capability <cap>", "Filter by capability").option("--json", "Output raw JSON response").action(async (opts) => {
|
|
4606
|
+
im.command("discover").description("Discover available agents").option("--type <type>", "Filter by agent type").option("--capability <cap>", "Filter by capability").option("--online-only", "Only return agents currently online").option("--json", "Output raw JSON response").action(async (opts) => {
|
|
3823
4607
|
const client = getIMClient2();
|
|
3824
4608
|
try {
|
|
3825
4609
|
const discoverOpts = {};
|
|
3826
4610
|
if (opts.type) discoverOpts.type = opts.type;
|
|
3827
4611
|
if (opts.capability) discoverOpts.capability = opts.capability;
|
|
4612
|
+
if (opts.onlineOnly) {
|
|
4613
|
+
discoverOpts.status = "online";
|
|
4614
|
+
discoverOpts.onlineOnly = "true";
|
|
4615
|
+
}
|
|
3828
4616
|
const res = await client.im.contacts.discover(Object.keys(discoverOpts).length ? discoverOpts : void 0);
|
|
3829
4617
|
if (!res.ok) {
|
|
3830
4618
|
process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
|
|
@@ -3888,9 +4676,50 @@ function register(parent, getIMClient2, _getAPIClient) {
|
|
|
3888
4676
|
process.exit(1);
|
|
3889
4677
|
}
|
|
3890
4678
|
});
|
|
3891
|
-
im.command("conversations").description("List conversations").option("--unread", "Show only conversations with unread messages").option("--json", "Output raw JSON response").action(async (opts) => {
|
|
4679
|
+
im.command("conversations [conversation-id]").description("List conversations, or show details for one (use --members to list participants)").option("--unread", "Show only conversations with unread messages (list mode only)").option("--members", "When a conversation id is provided, list its agent + human participants").option("--json", "Output raw JSON response").action(async (conversationId, opts) => {
|
|
3892
4680
|
const client = getIMClient2();
|
|
3893
4681
|
try {
|
|
4682
|
+
if (conversationId) {
|
|
4683
|
+
const res2 = await client.im.conversations.get(conversationId);
|
|
4684
|
+
if (!res2.ok) {
|
|
4685
|
+
process.stderr.write(`Error: ${res2.error?.message || JSON.stringify(res2.error)}
|
|
4686
|
+
`);
|
|
4687
|
+
process.exit(1);
|
|
4688
|
+
}
|
|
4689
|
+
const conv = res2.data;
|
|
4690
|
+
if (opts.json) {
|
|
4691
|
+
process.stdout.write(JSON.stringify(opts.members ? conv?.participants ?? [] : conv, null, 2) + "\n");
|
|
4692
|
+
return;
|
|
4693
|
+
}
|
|
4694
|
+
if (opts.members) {
|
|
4695
|
+
const participants = conv?.participants ?? [];
|
|
4696
|
+
if (participants.length === 0) {
|
|
4697
|
+
process.stdout.write("No participants.\n");
|
|
4698
|
+
return;
|
|
4699
|
+
}
|
|
4700
|
+
process.stdout.write(
|
|
4701
|
+
"User ID".padEnd(36) + "Role".padEnd(12) + "Type".padEnd(10) + "Display Name\n"
|
|
4702
|
+
);
|
|
4703
|
+
for (const p of participants) {
|
|
4704
|
+
const user = p.user ?? {};
|
|
4705
|
+
process.stdout.write(
|
|
4706
|
+
`${String(user.id ?? "").padEnd(36)}${String(p.role ?? "").padEnd(12)}${String(user.role ?? "").padEnd(10)}${String(user.displayName ?? user.username ?? "")}
|
|
4707
|
+
`
|
|
4708
|
+
);
|
|
4709
|
+
}
|
|
4710
|
+
return;
|
|
4711
|
+
}
|
|
4712
|
+
process.stdout.write(`ID: ${conv?.id ?? ""}
|
|
4713
|
+
`);
|
|
4714
|
+
process.stdout.write(`Type: ${conv?.type ?? ""}
|
|
4715
|
+
`);
|
|
4716
|
+
process.stdout.write(`Title: ${conv?.title ?? ""}
|
|
4717
|
+
`);
|
|
4718
|
+
const participantCount = Array.isArray(conv?.participants) ? (conv?.participants).length : 0;
|
|
4719
|
+
process.stdout.write(`Participants: ${participantCount}
|
|
4720
|
+
`);
|
|
4721
|
+
return;
|
|
4722
|
+
}
|
|
3894
4723
|
const listOpts = {};
|
|
3895
4724
|
if (opts.unread) {
|
|
3896
4725
|
listOpts.withUnread = true;
|
|
@@ -4200,9 +5029,9 @@ function register2(parent, _getIMClient, getAPIClient2) {
|
|
|
4200
5029
|
}
|
|
4201
5030
|
for (const item of results) {
|
|
4202
5031
|
process.stdout.write(`
|
|
4203
|
-
--- ${item.url ??
|
|
5032
|
+
--- ${item.url ?? "result"} ---
|
|
4204
5033
|
`);
|
|
4205
|
-
const hqcc = item.hqcc ?? item.
|
|
5034
|
+
const hqcc = item.hqcc ?? item.raw ?? "";
|
|
4206
5035
|
if (hqcc) {
|
|
4207
5036
|
const truncated = hqcc.length > 2e3 ? hqcc.slice(0, 2e3) + "... [truncated]" : hqcc;
|
|
4208
5037
|
process.stdout.write(truncated + "\n");
|
|
@@ -4242,9 +5071,9 @@ function register2(parent, _getIMClient, getAPIClient2) {
|
|
|
4242
5071
|
|
|
4243
5072
|
`);
|
|
4244
5073
|
results.forEach((item, i) => {
|
|
4245
|
-
process.stdout.write(`[${i + 1}] ${item.url ??
|
|
5074
|
+
process.stdout.write(`[${i + 1}] ${item.url ?? "result"}
|
|
4246
5075
|
`);
|
|
4247
|
-
const hqcc = item.hqcc ?? item.
|
|
5076
|
+
const hqcc = item.hqcc ?? item.raw ?? "";
|
|
4248
5077
|
if (hqcc) {
|
|
4249
5078
|
const truncated = hqcc.length > 2e3 ? hqcc.slice(0, 2e3) + "... [truncated]" : hqcc;
|
|
4250
5079
|
process.stdout.write(truncated + "\n");
|
|
@@ -4283,6 +5112,8 @@ function register2(parent, _getIMClient, getAPIClient2) {
|
|
|
4283
5112
|
}
|
|
4284
5113
|
|
|
4285
5114
|
// src/commands/evolve.ts
|
|
5115
|
+
var GENE_CATEGORIES = /* @__PURE__ */ new Set(["repair", "optimize", "innovate", "diagnostic"]);
|
|
5116
|
+
var GENE_SORTS = /* @__PURE__ */ new Set(["newest", "most_used", "highest_success"]);
|
|
4286
5117
|
function parseSignals(raw) {
|
|
4287
5118
|
if (!raw) return void 0;
|
|
4288
5119
|
const trimmed = raw.trim();
|
|
@@ -4295,6 +5126,21 @@ function parseSignals(raw) {
|
|
|
4295
5126
|
}
|
|
4296
5127
|
return trimmed.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4297
5128
|
}
|
|
5129
|
+
function parseOutcome(raw) {
|
|
5130
|
+
if (raw === "success" || raw === "failed") return raw;
|
|
5131
|
+
if (raw === "failure") return "failed";
|
|
5132
|
+
throw new Error(`Invalid outcome "${raw}". Expected success or failed.`);
|
|
5133
|
+
}
|
|
5134
|
+
function parseGeneCategory(raw) {
|
|
5135
|
+
if (!raw) return void 0;
|
|
5136
|
+
if (GENE_CATEGORIES.has(raw)) return raw;
|
|
5137
|
+
throw new Error(`Invalid gene category "${raw}". Expected repair, optimize, innovate, or diagnostic.`);
|
|
5138
|
+
}
|
|
5139
|
+
function parseGeneSort(raw) {
|
|
5140
|
+
if (!raw) return void 0;
|
|
5141
|
+
if (GENE_SORTS.has(raw)) return raw;
|
|
5142
|
+
throw new Error(`Invalid sort "${raw}". Expected newest, most_used, or highest_success.`);
|
|
5143
|
+
}
|
|
4298
5144
|
function handleError(err) {
|
|
4299
5145
|
const message = err instanceof Error ? err.message : String(err);
|
|
4300
5146
|
process.stderr.write(`Error: ${message}
|
|
@@ -4358,14 +5204,15 @@ function register3(parent, getIMClient2, _getAPIClient) {
|
|
|
4358
5204
|
evolve.command("record").description("Record an outcome against an evolution gene").requiredOption("-g, --gene <id>", "gene ID to record against").requiredOption("-o, --outcome <outcome>", "outcome: success, failure, partial").option("-s, --signals <signals>", "signals as JSON array or comma-separated list").option("--score <n>", "outcome score (0-1)").option("--summary <text>", "brief summary of the outcome").option("--scope <scope>", "evolution scope (default: global)").option("--json", "output raw JSON response").action(async (opts) => {
|
|
4359
5205
|
const client = getIMClient2();
|
|
4360
5206
|
try {
|
|
4361
|
-
const signals = parseSignals(opts.signals);
|
|
5207
|
+
const signals = parseSignals(opts.signals) ?? [];
|
|
4362
5208
|
const score = opts.score !== void 0 ? parseFloat(opts.score) : void 0;
|
|
5209
|
+
const outcome = parseOutcome(opts.outcome);
|
|
4363
5210
|
const res = await client.im.evolution.record({
|
|
4364
5211
|
gene_id: opts.gene,
|
|
4365
5212
|
signals,
|
|
4366
|
-
outcome
|
|
5213
|
+
outcome,
|
|
4367
5214
|
score,
|
|
4368
|
-
summary: opts.summary,
|
|
5215
|
+
summary: opts.summary ?? "",
|
|
4369
5216
|
scope: opts.scope
|
|
4370
5217
|
});
|
|
4371
5218
|
if (opts.json) {
|
|
@@ -4382,7 +5229,7 @@ function register3(parent, getIMClient2, _getAPIClient) {
|
|
|
4382
5229
|
try {
|
|
4383
5230
|
const res = await client.im.evolution.submitReport({
|
|
4384
5231
|
rawContext: opts.error,
|
|
4385
|
-
outcome: opts.status,
|
|
5232
|
+
outcome: parseOutcome(opts.status),
|
|
4386
5233
|
taskContext: opts.task
|
|
4387
5234
|
});
|
|
4388
5235
|
if (opts.json && !opts.wait) {
|
|
@@ -4416,7 +5263,7 @@ function register3(parent, getIMClient2, _getAPIClient) {
|
|
|
4416
5263
|
const maxIterations = 30;
|
|
4417
5264
|
let lastStatus;
|
|
4418
5265
|
for (let i = 0; i < maxIterations; i++) {
|
|
4419
|
-
await new Promise((
|
|
5266
|
+
await new Promise((resolve3) => setTimeout(resolve3, 2e3));
|
|
4420
5267
|
if (!opts.json) process.stdout.write(".");
|
|
4421
5268
|
const statusRes = await client.im.evolution.getReportStatus(traceId);
|
|
4422
5269
|
if (!statusRes.ok) break;
|
|
@@ -4476,7 +5323,7 @@ function register3(parent, getIMClient2, _getAPIClient) {
|
|
|
4476
5323
|
try {
|
|
4477
5324
|
const signals_match = parseSignals(opts.signals) ?? [];
|
|
4478
5325
|
const res = await client.im.evolution.createGene({
|
|
4479
|
-
category: opts.category,
|
|
5326
|
+
category: parseGeneCategory(opts.category),
|
|
4480
5327
|
signals_match,
|
|
4481
5328
|
strategy: opts.strategy,
|
|
4482
5329
|
title: opts.name,
|
|
@@ -4682,9 +5529,9 @@ function register3(parent, getIMClient2, _getAPIClient) {
|
|
|
4682
5529
|
try {
|
|
4683
5530
|
const limit = parseInt(opts.limit ?? "20", 10);
|
|
4684
5531
|
const res = await client.im.evolution.browseGenes({
|
|
4685
|
-
category: opts.category,
|
|
5532
|
+
category: parseGeneCategory(opts.category),
|
|
4686
5533
|
search: opts.search,
|
|
4687
|
-
sort: opts.sort,
|
|
5534
|
+
sort: parseGeneSort(opts.sort),
|
|
4688
5535
|
limit
|
|
4689
5536
|
});
|
|
4690
5537
|
if (opts.json) {
|
|
@@ -4799,17 +5646,89 @@ function register3(parent, getIMClient2, _getAPIClient) {
|
|
|
4799
5646
|
}
|
|
4800
5647
|
|
|
4801
5648
|
// src/commands/task.ts
|
|
5649
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned", "running", "review", "completed", "failed", "cancelled"]);
|
|
5650
|
+
var TASK_PRIORITIES = /* @__PURE__ */ new Set(["low", "medium", "high", "urgent"]);
|
|
5651
|
+
var TASK_KINDS = /* @__PURE__ */ new Set(["work_item", "goal"]);
|
|
5652
|
+
function parseTaskStatus(raw) {
|
|
5653
|
+
if (!raw) return void 0;
|
|
5654
|
+
if (TASK_STATUSES.has(raw)) return raw;
|
|
5655
|
+
throw new Error(`Invalid task status "${raw}".`);
|
|
5656
|
+
}
|
|
5657
|
+
async function resolveAssigneeId(client, name) {
|
|
5658
|
+
const needle = name.trim().toLowerCase();
|
|
5659
|
+
const normalized = needle.replace(/^@/, "");
|
|
5660
|
+
if (!needle) return "";
|
|
5661
|
+
const res = await client.im.contacts.discover();
|
|
5662
|
+
if (!res.ok || !Array.isArray(res.data)) return "";
|
|
5663
|
+
const agents = res.data;
|
|
5664
|
+
const exact = agents.find((agent) => {
|
|
5665
|
+
const values = [agent.userId, agent.username, agent.displayName].map(
|
|
5666
|
+
(v) => typeof v === "string" ? v.trim().toLowerCase() : ""
|
|
5667
|
+
);
|
|
5668
|
+
return values.includes(needle) || values.includes(normalized);
|
|
5669
|
+
});
|
|
5670
|
+
if (exact && typeof exact.userId === "string") return exact.userId;
|
|
5671
|
+
const fuzzy = agents.find((agent) => {
|
|
5672
|
+
const labels = [agent.username, agent.displayName].map((v) => typeof v === "string" ? v.trim().toLowerCase() : "").filter(Boolean);
|
|
5673
|
+
return labels.some((label) => label.includes(normalized) || normalized.includes(label));
|
|
5674
|
+
});
|
|
5675
|
+
return fuzzy && typeof fuzzy.userId === "string" ? fuzzy.userId : "";
|
|
5676
|
+
}
|
|
4802
5677
|
function register4(parent, getIMClient2, _getAPIClient) {
|
|
4803
5678
|
const task = parent.command("task").description("Manage tasks in the task marketplace");
|
|
4804
|
-
task.command("create").description("Create a new task").requiredOption("--title <title>", "task title").option("--description <description>", "task description").option("--capability <capability>", "required agent capability").option("--budget <budget>", "budget in credits", parseFloat).option("--json", "output raw JSON response").action(async (opts) => {
|
|
5679
|
+
task.command("create").description("Create a new task").requiredOption("--title <title>", "task title").option("--description <description>", "task description").option("--capability <capability>", "required agent capability").option("--budget <budget>", "budget in credits", parseFloat).option("--reward <reward>", "alias for --budget (credits offered for completion)", parseFloat).option("--priority <priority>", "task priority: low | medium | high | urgent").option("--assignee-id <imUserId>", "directly assign to a specific agent by IM user id").option("--assignee-name <name>", "assign by @username / display name (resolved via discover)").option("--conversation-id <id>", "pin task to a conversation/session").option("--kind <kind>", "board projection kind: work_item (default) | goal", "work_item").option("--schedule-at <iso>", "one-shot ISO 8601 scheduled time (sets scheduleType=once)").option("--schedule-cron <expr>", "cron expression (sets scheduleType=cron)").option("--json", "output raw JSON response").action(async (opts) => {
|
|
4805
5680
|
const client = getIMClient2();
|
|
4806
5681
|
try {
|
|
4807
|
-
|
|
5682
|
+
if (opts.priority && !TASK_PRIORITIES.has(opts.priority)) {
|
|
5683
|
+
throw new Error(`Invalid --priority "${opts.priority}". Use one of: low, medium, high, urgent.`);
|
|
5684
|
+
}
|
|
5685
|
+
const kind = opts.kind ?? "work_item";
|
|
5686
|
+
if (!TASK_KINDS.has(kind)) {
|
|
5687
|
+
throw new Error(`Invalid --kind "${kind}". Use one of: work_item, goal.`);
|
|
5688
|
+
}
|
|
5689
|
+
let assigneeId = opts.assigneeId;
|
|
5690
|
+
if (!assigneeId && opts.assigneeName) {
|
|
5691
|
+
assigneeId = await resolveAssigneeId(client, opts.assigneeName);
|
|
5692
|
+
if (!assigneeId) {
|
|
5693
|
+
throw new Error(
|
|
5694
|
+
`--assignee-name "${opts.assigneeName}" did not resolve to an agent. Use a visible agent username/display name or pass --assignee-id explicitly.`
|
|
5695
|
+
);
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5698
|
+
const budget = opts.budget ?? opts.reward;
|
|
5699
|
+
const metadata = { kind };
|
|
5700
|
+
if (opts.priority) metadata.priority = opts.priority;
|
|
5701
|
+
if (opts.conversationId) {
|
|
5702
|
+
metadata.context = { linkedConversationId: opts.conversationId };
|
|
5703
|
+
}
|
|
5704
|
+
if (kind === "goal") {
|
|
5705
|
+
metadata.intent = "standing_objective";
|
|
5706
|
+
metadata.goal = {
|
|
5707
|
+
status: "active",
|
|
5708
|
+
priority: opts.priority === "urgent" ? "high" : opts.priority ?? "medium",
|
|
5709
|
+
linkedConversationIds: opts.conversationId ? [opts.conversationId] : [],
|
|
5710
|
+
linkedTaskIds: [],
|
|
5711
|
+
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5712
|
+
};
|
|
5713
|
+
}
|
|
5714
|
+
const createOpts = {
|
|
4808
5715
|
title: opts.title,
|
|
4809
5716
|
description: opts.description,
|
|
4810
5717
|
capability: opts.capability,
|
|
4811
|
-
budget
|
|
4812
|
-
|
|
5718
|
+
budget,
|
|
5719
|
+
metadata
|
|
5720
|
+
};
|
|
5721
|
+
if (assigneeId) createOpts.assigneeId = assigneeId;
|
|
5722
|
+
if (opts.conversationId) createOpts.conversationId = opts.conversationId;
|
|
5723
|
+
if (opts.scheduleAt) {
|
|
5724
|
+
createOpts.scheduleType = "once";
|
|
5725
|
+
createOpts.scheduleAt = opts.scheduleAt;
|
|
5726
|
+
}
|
|
5727
|
+
if (opts.scheduleCron) {
|
|
5728
|
+
createOpts.scheduleType = "cron";
|
|
5729
|
+
createOpts.scheduleCron = opts.scheduleCron;
|
|
5730
|
+
}
|
|
5731
|
+
const res = await client.im.tasks.create(createOpts);
|
|
4813
5732
|
if (opts.json) {
|
|
4814
5733
|
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
4815
5734
|
return;
|
|
@@ -4846,7 +5765,7 @@ function register4(parent, getIMClient2, _getAPIClient) {
|
|
|
4846
5765
|
const client = getIMClient2();
|
|
4847
5766
|
try {
|
|
4848
5767
|
const res = await client.im.tasks.list({
|
|
4849
|
-
status: opts.status,
|
|
5768
|
+
status: parseTaskStatus(opts.status),
|
|
4850
5769
|
capability: opts.capability,
|
|
4851
5770
|
limit: parseInt(opts.limit, 10)
|
|
4852
5771
|
});
|
|
@@ -4900,7 +5819,8 @@ ${tasks.length} task(s) listed.
|
|
|
4900
5819
|
`);
|
|
4901
5820
|
process.exit(1);
|
|
4902
5821
|
}
|
|
4903
|
-
const
|
|
5822
|
+
const detail = res.data;
|
|
5823
|
+
const t = detail.task;
|
|
4904
5824
|
process.stdout.write(`ID: ${t.id}
|
|
4905
5825
|
`);
|
|
4906
5826
|
process.stdout.write(`Title: ${t.title}
|
|
@@ -4931,14 +5851,14 @@ ${tasks.length} task(s) listed.
|
|
|
4931
5851
|
`);
|
|
4932
5852
|
if (t.error) process.stdout.write(`Error: ${t.error}
|
|
4933
5853
|
`);
|
|
4934
|
-
const logs =
|
|
5854
|
+
const logs = detail.logs ?? [];
|
|
4935
5855
|
if (logs.length > 0) {
|
|
4936
5856
|
process.stdout.write(`
|
|
4937
5857
|
Logs (${logs.length}):
|
|
4938
5858
|
`);
|
|
4939
5859
|
for (const log of logs) {
|
|
4940
|
-
const ts = log.createdAt ??
|
|
4941
|
-
const msg = log.message ??
|
|
5860
|
+
const ts = log.createdAt ?? "";
|
|
5861
|
+
const msg = log.message ?? JSON.stringify(log);
|
|
4942
5862
|
process.stdout.write(` [${ts}] ${msg}
|
|
4943
5863
|
`);
|
|
4944
5864
|
}
|
|
@@ -5179,16 +6099,23 @@ Logs (${logs.length}):
|
|
|
5179
6099
|
}
|
|
5180
6100
|
|
|
5181
6101
|
// src/commands/memory.ts
|
|
6102
|
+
var MEMORY_TYPES = /* @__PURE__ */ new Set(["user", "feedback", "project", "reference"]);
|
|
5182
6103
|
function register5(parent, getIMClient2, _getAPIClient) {
|
|
5183
6104
|
const mem = parent.command("memory").description("Agent memory file management");
|
|
5184
|
-
mem.command("write").description("Write a memory file").requiredOption("-s, --scope <scope>", "memory scope").requiredOption("-p, --path <path>", "file path within scope").requiredOption("-c, --content <content>", "file content").option("--json", "output raw JSON response").action(async (opts) => {
|
|
6105
|
+
mem.command("write").description("Write a memory file").requiredOption("-s, --scope <scope>", "memory scope").requiredOption("-p, --path <path>", "file path within scope").requiredOption("-c, --content <content>", "file content").option("--type <type>", "memory type: user | feedback | project | reference").option("--description <description>", "human-readable description of this memory").option("--json", "output raw JSON response").action(async (opts) => {
|
|
5185
6106
|
const client = getIMClient2();
|
|
5186
6107
|
try {
|
|
5187
|
-
|
|
6108
|
+
if (opts.type && !MEMORY_TYPES.has(opts.type)) {
|
|
6109
|
+
throw new Error(`Invalid --type "${opts.type}". Use one of: user, feedback, project, reference.`);
|
|
6110
|
+
}
|
|
6111
|
+
const body = {
|
|
5188
6112
|
scope: opts.scope,
|
|
5189
6113
|
path: opts.path,
|
|
5190
6114
|
content: opts.content
|
|
5191
|
-
}
|
|
6115
|
+
};
|
|
6116
|
+
if (opts.type) body.memoryType = opts.type;
|
|
6117
|
+
if (opts.description) body.description = opts.description;
|
|
6118
|
+
const res = await client.im.memory.createFile(body);
|
|
5192
6119
|
if (opts.json) {
|
|
5193
6120
|
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
5194
6121
|
return;
|
|
@@ -5199,6 +6126,7 @@ function register5(parent, getIMClient2, _getAPIClient) {
|
|
|
5199
6126
|
process.exit(1);
|
|
5200
6127
|
}
|
|
5201
6128
|
const file = res.data;
|
|
6129
|
+
if (!file) throw new Error("Memory file response missing data");
|
|
5202
6130
|
process.stdout.write(`Memory file created
|
|
5203
6131
|
`);
|
|
5204
6132
|
process.stdout.write(` ID: ${file.id}
|
|
@@ -5229,6 +6157,7 @@ function register5(parent, getIMClient2, _getAPIClient) {
|
|
|
5229
6157
|
process.exit(1);
|
|
5230
6158
|
}
|
|
5231
6159
|
const file = res.data;
|
|
6160
|
+
if (!file) throw new Error("Memory file response missing data");
|
|
5232
6161
|
process.stdout.write(`ID: ${file.id}
|
|
5233
6162
|
`);
|
|
5234
6163
|
process.stdout.write(`Scope: ${file.scope}
|
|
@@ -5246,7 +6175,9 @@ ${file.content ?? ""}
|
|
|
5246
6175
|
});
|
|
5247
6176
|
if (opts.json) {
|
|
5248
6177
|
if (listRes.ok && Array.isArray(listRes.data) && listRes.data.length === 1) {
|
|
5249
|
-
const
|
|
6178
|
+
const first = listRes.data[0];
|
|
6179
|
+
if (!first) throw new Error("Memory file response missing data");
|
|
6180
|
+
const detailRes = await client.im.memory.getFile(first.id);
|
|
5250
6181
|
process.stdout.write(JSON.stringify(detailRes, null, 2) + "\n");
|
|
5251
6182
|
} else {
|
|
5252
6183
|
process.stdout.write(JSON.stringify(listRes, null, 2) + "\n");
|
|
@@ -5264,13 +6195,16 @@ ${file.content ?? ""}
|
|
|
5264
6195
|
return;
|
|
5265
6196
|
}
|
|
5266
6197
|
if (files.length === 1) {
|
|
5267
|
-
const
|
|
6198
|
+
const first = files[0];
|
|
6199
|
+
if (!first) throw new Error("Memory file response missing data");
|
|
6200
|
+
const detailRes = await client.im.memory.getFile(first.id);
|
|
5268
6201
|
if (!detailRes.ok) {
|
|
5269
6202
|
process.stderr.write(`Error: ${detailRes.error?.message || "Unknown error"}
|
|
5270
6203
|
`);
|
|
5271
6204
|
process.exit(1);
|
|
5272
6205
|
}
|
|
5273
6206
|
const file = detailRes.data;
|
|
6207
|
+
if (!file) throw new Error("Memory file response missing data");
|
|
5274
6208
|
process.stdout.write(`ID: ${file.id}
|
|
5275
6209
|
`);
|
|
5276
6210
|
process.stdout.write(`Scope: ${file.scope}
|
|
@@ -5338,10 +6272,10 @@ ${file.content ?? ""}
|
|
|
5338
6272
|
process.exit(1);
|
|
5339
6273
|
}
|
|
5340
6274
|
});
|
|
5341
|
-
mem.command("compact <conversation-id>").description("Create a compaction summary for a conversation").option("--json", "output raw JSON response").action(async (conversationId, opts) => {
|
|
6275
|
+
mem.command("compact <conversation-id>").description("Create a compaction summary for a conversation").requiredOption("--summary <text>", "summary text to persist").option("--json", "output raw JSON response").action(async (conversationId, opts) => {
|
|
5342
6276
|
const client = getIMClient2();
|
|
5343
6277
|
try {
|
|
5344
|
-
const res = await client.im.memory.compact({ conversationId });
|
|
6278
|
+
const res = await client.im.memory.compact({ conversationId, summary: opts.summary });
|
|
5345
6279
|
if (opts.json) {
|
|
5346
6280
|
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
5347
6281
|
return;
|
|
@@ -5365,6 +6299,66 @@ ${file.content ?? ""}
|
|
|
5365
6299
|
} catch (err) {
|
|
5366
6300
|
const message = err instanceof Error ? err.message : String(err);
|
|
5367
6301
|
process.stderr.write(`Error: ${message}
|
|
6302
|
+
`);
|
|
6303
|
+
process.exit(1);
|
|
6304
|
+
}
|
|
6305
|
+
});
|
|
6306
|
+
mem.command("extract").description("Extract structured memory entries from a session journal (v1.8.0 P1)").requiredOption("--journal <text>", "session journal text (min 50 chars)").option("--scope <scope>", "evolution scope", "global").option("--json", "output raw JSON response").action(async (opts) => {
|
|
6307
|
+
const client = getIMClient2();
|
|
6308
|
+
try {
|
|
6309
|
+
const res = await client.im.memory._r("POST", "/api/im/memory/extract", { journal: opts.journal, scope: opts.scope });
|
|
6310
|
+
if (opts.json) {
|
|
6311
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
6312
|
+
return;
|
|
6313
|
+
}
|
|
6314
|
+
if (!res.ok) {
|
|
6315
|
+
process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
|
|
6316
|
+
`);
|
|
6317
|
+
process.exit(1);
|
|
6318
|
+
}
|
|
6319
|
+
process.stdout.write("Memory extraction complete\n");
|
|
6320
|
+
const data = res.data;
|
|
6321
|
+
if (data) {
|
|
6322
|
+
if (Array.isArray(data.extracted)) {
|
|
6323
|
+
process.stdout.write(` Extracted: ${data.extracted.length}
|
|
6324
|
+
`);
|
|
6325
|
+
}
|
|
6326
|
+
if (Array.isArray(data.created)) {
|
|
6327
|
+
process.stdout.write(` Created: ${data.created.length}
|
|
6328
|
+
`);
|
|
6329
|
+
}
|
|
6330
|
+
if (Array.isArray(data.updated)) {
|
|
6331
|
+
process.stdout.write(` Updated: ${data.updated.length}
|
|
6332
|
+
`);
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
} catch (err) {
|
|
6336
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6337
|
+
process.stderr.write(`Error: ${message}
|
|
6338
|
+
`);
|
|
6339
|
+
process.exit(1);
|
|
6340
|
+
}
|
|
6341
|
+
});
|
|
6342
|
+
mem.command("consolidate").description("Trigger Dream consolidation (cluster + reflect across memories)").option("--scope <scope>", "evolution scope", "global").option("--json", "output raw JSON response").action(async (opts) => {
|
|
6343
|
+
const client = getIMClient2();
|
|
6344
|
+
try {
|
|
6345
|
+
const res = await client.im.memory._r("POST", "/api/im/memory/consolidate", { scope: opts.scope });
|
|
6346
|
+
if (opts.json) {
|
|
6347
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6350
|
+
if (!res.ok) {
|
|
6351
|
+
process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
|
|
6352
|
+
`);
|
|
6353
|
+
process.exit(1);
|
|
6354
|
+
}
|
|
6355
|
+
process.stdout.write("Dream consolidation triggered\n");
|
|
6356
|
+
if (res.data && typeof res.data === "object") {
|
|
6357
|
+
process.stdout.write(JSON.stringify(res.data, null, 2) + "\n");
|
|
6358
|
+
}
|
|
6359
|
+
} catch (err) {
|
|
6360
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6361
|
+
process.stderr.write(`Error: ${message}
|
|
5368
6362
|
`);
|
|
5369
6363
|
process.exit(1);
|
|
5370
6364
|
}
|
|
@@ -5405,7 +6399,7 @@ function printFileTable(files) {
|
|
|
5405
6399
|
const idLen = Math.max(2, ...files.map((f) => f.id.length));
|
|
5406
6400
|
const scopeLen = Math.max(5, ...files.map((f) => f.scope.length));
|
|
5407
6401
|
const pathLen = Math.max(4, ...files.map((f) => f.path.length));
|
|
5408
|
-
const row = (id, scope,
|
|
6402
|
+
const row = (id, scope, path5) => `${id.padEnd(idLen)} ${scope.padEnd(scopeLen)} ${path5.padEnd(pathLen)}`;
|
|
5409
6403
|
process.stdout.write(row("ID", "SCOPE", "PATH") + "\n");
|
|
5410
6404
|
process.stdout.write(`${"-".repeat(idLen)} ${"-".repeat(scopeLen)} ${"-".repeat(pathLen)}
|
|
5411
6405
|
`);
|
|
@@ -5415,6 +6409,17 @@ function printFileTable(files) {
|
|
|
5415
6409
|
}
|
|
5416
6410
|
|
|
5417
6411
|
// src/commands/skill.ts
|
|
6412
|
+
var SKILL_LOCAL_PLATFORMS = /* @__PURE__ */ new Set(["claude-code", "openclaw", "opencode", "plugin"]);
|
|
6413
|
+
function parsePlatforms(raw) {
|
|
6414
|
+
if (raw === "all") return void 0;
|
|
6415
|
+
const values = raw.split(",").map((item) => item.trim()).filter(Boolean);
|
|
6416
|
+
for (const value of values) {
|
|
6417
|
+
if (!SKILL_LOCAL_PLATFORMS.has(value)) {
|
|
6418
|
+
throw new Error(`Invalid platform "${value}". Expected claude-code, openclaw, opencode, plugin, or all.`);
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
return values;
|
|
6422
|
+
}
|
|
5418
6423
|
function padEnd(str, len) {
|
|
5419
6424
|
if (str.length >= len) return str.slice(0, len);
|
|
5420
6425
|
return str + " ".repeat(len - str.length);
|
|
@@ -5475,10 +6480,11 @@ function register6(parent, getIMClient2, _getAPIClient) {
|
|
|
5475
6480
|
if (!opts.local) {
|
|
5476
6481
|
res = await client.im.evolution.installSkill(slug);
|
|
5477
6482
|
} else {
|
|
5478
|
-
const platforms = opts.platform
|
|
6483
|
+
const platforms = parsePlatforms(opts.platform);
|
|
5479
6484
|
res = await client.im.evolution.installSkillLocal(slug, {
|
|
5480
6485
|
platforms,
|
|
5481
|
-
project: opts.project
|
|
6486
|
+
project: Boolean(opts.project),
|
|
6487
|
+
projectRoot: opts.project
|
|
5482
6488
|
});
|
|
5483
6489
|
}
|
|
5484
6490
|
if (opts.json) {
|
|
@@ -5622,7 +6628,7 @@ ${result.content}
|
|
|
5622
6628
|
skill.command("sync").description("Re-sync all installed skills to local filesystem").option("--platform <platform>", "target platform: claude-code, openclaw, opencode, or all", "all").option("--json", "output raw JSON response").action(async (opts) => {
|
|
5623
6629
|
const client = getIMClient2();
|
|
5624
6630
|
try {
|
|
5625
|
-
const platforms = opts.platform
|
|
6631
|
+
const platforms = parsePlatforms(opts.platform);
|
|
5626
6632
|
const res = await client.im.evolution.syncSkillsLocal({ platforms });
|
|
5627
6633
|
if (opts.json) {
|
|
5628
6634
|
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
@@ -6292,9 +7298,487 @@ function register10(parent, getIMClient2, _getAPIClient) {
|
|
|
6292
7298
|
});
|
|
6293
7299
|
}
|
|
6294
7300
|
|
|
6295
|
-
// src/
|
|
7301
|
+
// src/commands/asset.ts
|
|
6296
7302
|
var fs2 = __toESM(require("fs"));
|
|
6297
7303
|
var path2 = __toESM(require("path"));
|
|
7304
|
+
function resolveWorkspaceId(flag) {
|
|
7305
|
+
if (flag) return flag;
|
|
7306
|
+
if (typeof process !== "undefined" && process.env?.PRISMER_WORKSPACE_ID) {
|
|
7307
|
+
return process.env.PRISMER_WORKSPACE_ID;
|
|
7308
|
+
}
|
|
7309
|
+
return void 0;
|
|
7310
|
+
}
|
|
7311
|
+
function requireWorkspaceId(flag) {
|
|
7312
|
+
const wsId = resolveWorkspaceId(flag);
|
|
7313
|
+
if (!wsId) {
|
|
7314
|
+
process.stderr.write("Error: --workspace-id is required (or set PRISMER_WORKSPACE_ID env var).\n");
|
|
7315
|
+
process.exit(1);
|
|
7316
|
+
}
|
|
7317
|
+
return wsId;
|
|
7318
|
+
}
|
|
7319
|
+
function globToRegex(glob) {
|
|
7320
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
7321
|
+
const pattern = escaped.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
7322
|
+
return new RegExp(`^${pattern}$`, "i");
|
|
7323
|
+
}
|
|
7324
|
+
function assetFilename(a) {
|
|
7325
|
+
if (a.filename) return a.filename;
|
|
7326
|
+
const meta = a.metadata;
|
|
7327
|
+
if (meta) {
|
|
7328
|
+
const fn = meta["filename"] ?? meta["fileName"] ?? meta["name"];
|
|
7329
|
+
if (typeof fn === "string") return fn;
|
|
7330
|
+
}
|
|
7331
|
+
return null;
|
|
7332
|
+
}
|
|
7333
|
+
function formatBytes(n) {
|
|
7334
|
+
if (n == null) return "-";
|
|
7335
|
+
return String(n);
|
|
7336
|
+
}
|
|
7337
|
+
function register11(parent, getIMClient2, _getAPIClient) {
|
|
7338
|
+
const asset = parent.command("asset").description("Inspect and manage workspace assets (content-addressed)");
|
|
7339
|
+
asset.command("list").description("List assets in a workspace").option("--workspace-id <id>", "workspace id (defaults to PRISMER_WORKSPACE_ID env)").option("--filename <glob>", 'filter by filename glob (client-side, e.g. "design-doc*")').option("--mime <type>", 'filter by MIME type prefix (e.g. "application/pdf")').option("--kind <kind>", "filter by row-level kind (e.g. file, image, sandbox-output)").option("--updated-after <iso>", "filter to assets created/updated after this ISO timestamp").option("--task-id <id>", "filter by source task id").option("-n, --limit <n>", "maximum results to return", "50").option("--json", "output raw JSON response").action(async (opts) => {
|
|
7340
|
+
const wsId = requireWorkspaceId(opts.workspaceId);
|
|
7341
|
+
const client = getIMClient2();
|
|
7342
|
+
try {
|
|
7343
|
+
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 50, 1), 200);
|
|
7344
|
+
const res = await client.im.assets.list({
|
|
7345
|
+
workspaceId: wsId,
|
|
7346
|
+
taskId: opts.taskId,
|
|
7347
|
+
kind: opts.kind,
|
|
7348
|
+
limit
|
|
7349
|
+
});
|
|
7350
|
+
if (!res.ok) {
|
|
7351
|
+
if (opts.json) process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
7352
|
+
else process.stderr.write(`Error: ${res.error?.message || "list failed"}
|
|
7353
|
+
`);
|
|
7354
|
+
process.exit(1);
|
|
7355
|
+
}
|
|
7356
|
+
let rows = res.data ?? [];
|
|
7357
|
+
if (opts.filename) {
|
|
7358
|
+
const re = globToRegex(opts.filename);
|
|
7359
|
+
rows = rows.filter((r) => {
|
|
7360
|
+
const fn = assetFilename(r);
|
|
7361
|
+
return fn != null && re.test(fn);
|
|
7362
|
+
});
|
|
7363
|
+
}
|
|
7364
|
+
if (opts.mime) {
|
|
7365
|
+
const m = opts.mime.toLowerCase();
|
|
7366
|
+
rows = rows.filter((r) => (r.mime ?? "").toLowerCase().startsWith(m));
|
|
7367
|
+
}
|
|
7368
|
+
if (opts.updatedAfter) {
|
|
7369
|
+
const after = Date.parse(opts.updatedAfter);
|
|
7370
|
+
if (!Number.isNaN(after)) {
|
|
7371
|
+
rows = rows.filter((r) => {
|
|
7372
|
+
const ts = Date.parse(r.createdAt ?? "");
|
|
7373
|
+
return !Number.isNaN(ts) && ts >= after;
|
|
7374
|
+
});
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
if (opts.json) {
|
|
7378
|
+
process.stdout.write(JSON.stringify({ ok: true, data: rows }, null, 2) + "\n");
|
|
7379
|
+
return;
|
|
7380
|
+
}
|
|
7381
|
+
if (rows.length === 0) {
|
|
7382
|
+
process.stdout.write("No assets found.\n");
|
|
7383
|
+
return;
|
|
7384
|
+
}
|
|
7385
|
+
const idW = 24;
|
|
7386
|
+
const mimeW = 20;
|
|
7387
|
+
const sizeW = 12;
|
|
7388
|
+
const hashW = 16;
|
|
7389
|
+
const header = "ID".padEnd(idW) + "MIME".padEnd(mimeW) + "SIZE".padEnd(sizeW) + "HASH".padEnd(hashW) + "NAME";
|
|
7390
|
+
process.stdout.write(header + "\n");
|
|
7391
|
+
process.stdout.write("-".repeat(idW + mimeW + sizeW + hashW + 20) + "\n");
|
|
7392
|
+
for (const r of rows) {
|
|
7393
|
+
const id = String(r.id).slice(0, idW - 1);
|
|
7394
|
+
const mime = (r.mime ?? "-").slice(0, mimeW - 1);
|
|
7395
|
+
const size = formatBytes(r.sizeBytes).slice(0, sizeW - 1);
|
|
7396
|
+
const hash = (r.contentHash ?? "-").slice(0, 12);
|
|
7397
|
+
const name = assetFilename(r) ?? "";
|
|
7398
|
+
process.stdout.write(
|
|
7399
|
+
id.padEnd(idW) + mime.padEnd(mimeW) + size.padEnd(sizeW) + hash.padEnd(hashW) + name + "\n"
|
|
7400
|
+
);
|
|
7401
|
+
}
|
|
7402
|
+
process.stdout.write(`
|
|
7403
|
+
${rows.length} asset(s) listed.
|
|
7404
|
+
`);
|
|
7405
|
+
} catch (err) {
|
|
7406
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
7407
|
+
`);
|
|
7408
|
+
process.exit(1);
|
|
7409
|
+
}
|
|
7410
|
+
});
|
|
7411
|
+
asset.command("get <assetId>").description("Show full asset metadata (no content)").option("--json", "output raw JSON response").action(async (assetId, opts) => {
|
|
7412
|
+
const client = getIMClient2();
|
|
7413
|
+
try {
|
|
7414
|
+
const res = await client.im.assets.detail(assetId);
|
|
7415
|
+
if (opts.json) {
|
|
7416
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
7417
|
+
if (!res.ok) process.exit(1);
|
|
7418
|
+
return;
|
|
7419
|
+
}
|
|
7420
|
+
if (!res.ok) {
|
|
7421
|
+
process.stderr.write(`Error: ${res.error?.message || "get failed"}
|
|
7422
|
+
`);
|
|
7423
|
+
process.exit(1);
|
|
7424
|
+
}
|
|
7425
|
+
const a = res.data;
|
|
7426
|
+
process.stdout.write(`ID: ${a.id}
|
|
7427
|
+
`);
|
|
7428
|
+
process.stdout.write(`Workspace: ${a.workspaceId}
|
|
7429
|
+
`);
|
|
7430
|
+
process.stdout.write(`Hash: ${a.contentHash}
|
|
7431
|
+
`);
|
|
7432
|
+
process.stdout.write(`MIME: ${a.mime ?? "-"}
|
|
7433
|
+
`);
|
|
7434
|
+
process.stdout.write(`Size: ${formatBytes(a.sizeBytes)} bytes
|
|
7435
|
+
`);
|
|
7436
|
+
process.stdout.write(`Kind: ${a.kind}
|
|
7437
|
+
`);
|
|
7438
|
+
process.stdout.write(`Storage URI: ${a.storageUri}
|
|
7439
|
+
`);
|
|
7440
|
+
process.stdout.write(`Created: ${a.createdAt}
|
|
7441
|
+
`);
|
|
7442
|
+
const fn = assetFilename(a);
|
|
7443
|
+
if (fn) process.stdout.write(`Filename: ${fn}
|
|
7444
|
+
`);
|
|
7445
|
+
if (a.sourceTaskId) process.stdout.write(`Source Task: ${a.sourceTaskId}
|
|
7446
|
+
`);
|
|
7447
|
+
if (a.sourceAgentImUserId) process.stdout.write(`Source Agent: ${a.sourceAgentImUserId}
|
|
7448
|
+
`);
|
|
7449
|
+
if (a.cdnUrl) process.stdout.write(`CDN URL: ${a.cdnUrl}
|
|
7450
|
+
`);
|
|
7451
|
+
if (a.url) process.stdout.write(`Signed URL: ${a.url} (expires in ${a.expiresIn ?? "?"}s)
|
|
7452
|
+
`);
|
|
7453
|
+
if (a.metadata && Object.keys(a.metadata).length > 0) {
|
|
7454
|
+
process.stdout.write(`Metadata: ${JSON.stringify(a.metadata)}
|
|
7455
|
+
`);
|
|
7456
|
+
}
|
|
7457
|
+
} catch (err) {
|
|
7458
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
7459
|
+
`);
|
|
7460
|
+
process.exit(1);
|
|
7461
|
+
}
|
|
7462
|
+
});
|
|
7463
|
+
asset.command("by-hash <sha256>").description("Look up an asset by content hash (sha256)").option("--workspace-id <id>", "workspace id (defaults to PRISMER_WORKSPACE_ID env)").option("--json", "output raw JSON response").action(async (sha256, opts) => {
|
|
7464
|
+
const wsId = requireWorkspaceId(opts.workspaceId);
|
|
7465
|
+
const client = getIMClient2();
|
|
7466
|
+
try {
|
|
7467
|
+
const res = await client.im.assets.byHash(sha256, wsId);
|
|
7468
|
+
if (opts.json) {
|
|
7469
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
7470
|
+
if (!res.ok) process.exit(1);
|
|
7471
|
+
return;
|
|
7472
|
+
}
|
|
7473
|
+
if (!res.ok) {
|
|
7474
|
+
process.stderr.write(`Error: ${res.error?.message || "by-hash failed"}
|
|
7475
|
+
`);
|
|
7476
|
+
process.exit(1);
|
|
7477
|
+
}
|
|
7478
|
+
const a = res.data;
|
|
7479
|
+
process.stdout.write(`ID: ${a.id}
|
|
7480
|
+
`);
|
|
7481
|
+
process.stdout.write(`Hash: ${a.contentHash}
|
|
7482
|
+
`);
|
|
7483
|
+
process.stdout.write(`MIME: ${a.mime ?? "-"}
|
|
7484
|
+
`);
|
|
7485
|
+
process.stdout.write(`Size: ${formatBytes(a.sizeBytes)} bytes
|
|
7486
|
+
`);
|
|
7487
|
+
process.stdout.write(`Kind: ${a.kind}
|
|
7488
|
+
`);
|
|
7489
|
+
} catch (err) {
|
|
7490
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
7491
|
+
`);
|
|
7492
|
+
process.exit(1);
|
|
7493
|
+
}
|
|
7494
|
+
});
|
|
7495
|
+
asset.command("upload <path>").description("Upload a local file as a workspace asset").option("--workspace-id <id>", "workspace id (defaults to PRISMER_WORKSPACE_ID env)").option("--conversation-id <id>", "pin upload to a conversation (stored in metadata)").option("--kind <kind>", "asset kind label", "user-upload").option("--task-id <id>", "source task id").option("--mime <type>", "override detected MIME type").option("--filename <name>", "override filename").option("--json", "output raw JSON response").action(async (filePath, opts) => {
|
|
7496
|
+
const wsId = requireWorkspaceId(opts.workspaceId);
|
|
7497
|
+
if (!fs2.existsSync(filePath)) {
|
|
7498
|
+
process.stderr.write(`Error: file not found: ${filePath}
|
|
7499
|
+
`);
|
|
7500
|
+
process.exit(1);
|
|
7501
|
+
}
|
|
7502
|
+
const client = getIMClient2();
|
|
7503
|
+
try {
|
|
7504
|
+
const metadata = {};
|
|
7505
|
+
if (opts.conversationId) metadata.conversationId = opts.conversationId;
|
|
7506
|
+
if (opts.filename) metadata.filename = opts.filename;
|
|
7507
|
+
const uploadOpts = {
|
|
7508
|
+
workspaceId: wsId,
|
|
7509
|
+
kind: opts.kind ?? "user-upload"
|
|
7510
|
+
};
|
|
7511
|
+
if (opts.taskId) uploadOpts.sourceTaskId = opts.taskId;
|
|
7512
|
+
if (opts.mime) uploadOpts.mimeType = opts.mime;
|
|
7513
|
+
if (opts.filename) uploadOpts.fileName = opts.filename;
|
|
7514
|
+
if (Object.keys(metadata).length > 0) uploadOpts.metadata = metadata;
|
|
7515
|
+
const res = await client.im.assets.upload(filePath, uploadOpts);
|
|
7516
|
+
if (opts.json) {
|
|
7517
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
7518
|
+
if (!res.ok) process.exit(1);
|
|
7519
|
+
return;
|
|
7520
|
+
}
|
|
7521
|
+
if (!res.ok) {
|
|
7522
|
+
process.stderr.write(`Error: ${res.error?.message || "upload failed"}
|
|
7523
|
+
`);
|
|
7524
|
+
process.exit(1);
|
|
7525
|
+
}
|
|
7526
|
+
const a = res.data;
|
|
7527
|
+
process.stdout.write(`Uploaded ${path2.basename(filePath)}
|
|
7528
|
+
`);
|
|
7529
|
+
process.stdout.write(` ID: ${a.id}
|
|
7530
|
+
`);
|
|
7531
|
+
process.stdout.write(` Hash: ${a.contentHash}
|
|
7532
|
+
`);
|
|
7533
|
+
process.stdout.write(` Size: ${formatBytes(a.sizeBytes)} bytes
|
|
7534
|
+
`);
|
|
7535
|
+
process.stdout.write(` MIME: ${a.mime ?? "-"}
|
|
7536
|
+
`);
|
|
7537
|
+
process.stdout.write(` Kind: ${a.kind}
|
|
7538
|
+
`);
|
|
7539
|
+
} catch (err) {
|
|
7540
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
7541
|
+
`);
|
|
7542
|
+
process.exit(1);
|
|
7543
|
+
}
|
|
7544
|
+
});
|
|
7545
|
+
asset.command("download <assetId>").description("Download asset bytes (to file or stdout)").option("--out <path>", "destination file (omit to write to stdout)").option("--offset <n>", "byte offset for partial download", parseIntOpt).option("--length <n>", "byte length for partial download", parseIntOpt).action(async (assetId, opts) => {
|
|
7546
|
+
try {
|
|
7547
|
+
const { bytes, truncated, totalSize } = await fetchAssetBytes(
|
|
7548
|
+
getIMClient2(),
|
|
7549
|
+
assetId,
|
|
7550
|
+
opts.offset,
|
|
7551
|
+
opts.length
|
|
7552
|
+
);
|
|
7553
|
+
if (opts.out) {
|
|
7554
|
+
fs2.writeFileSync(opts.out, bytes);
|
|
7555
|
+
const range = describeRange(opts.offset, opts.length, bytes.byteLength);
|
|
7556
|
+
process.stderr.write(
|
|
7557
|
+
`Downloaded ${assetId} -> ${opts.out} (${bytes.byteLength} bytes${range ? `, ${range}` : ""}${truncated ? ", truncated" : ""}${totalSize != null ? `, total=${totalSize}` : ""})
|
|
7558
|
+
`
|
|
7559
|
+
);
|
|
7560
|
+
} else {
|
|
7561
|
+
process.stdout.write(bytes);
|
|
7562
|
+
}
|
|
7563
|
+
} catch (err) {
|
|
7564
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
7565
|
+
`);
|
|
7566
|
+
process.exit(1);
|
|
7567
|
+
}
|
|
7568
|
+
});
|
|
7569
|
+
asset.command("read <assetId>").description("Read an asset as text (bounded by --offset/--length)").option("--offset <n>", "byte offset for read", parseIntOpt).option("--length <n>", "maximum bytes to read (recommended for large files)", parseIntOpt).option("--force", "read even if MIME is not text-like").action(async (assetId, opts) => {
|
|
7570
|
+
const client = getIMClient2();
|
|
7571
|
+
try {
|
|
7572
|
+
const detailRes = await client.im.assets.detail(assetId);
|
|
7573
|
+
if (!detailRes.ok) {
|
|
7574
|
+
process.stderr.write(`Error: ${detailRes.error?.message || "asset get failed"}
|
|
7575
|
+
`);
|
|
7576
|
+
process.exit(1);
|
|
7577
|
+
}
|
|
7578
|
+
const detail = detailRes.data;
|
|
7579
|
+
const mime = (detail.mime ?? "").toLowerCase();
|
|
7580
|
+
const looksTextual = mime.startsWith("text/") || mime === "application/json" || mime === "application/xml" || mime === "application/javascript" || mime === "application/x-yaml" || mime.endsWith("+json") || mime.endsWith("+xml");
|
|
7581
|
+
if (!looksTextual && !opts.force) {
|
|
7582
|
+
process.stderr.write(
|
|
7583
|
+
`Refusing to read non-text asset (mime=${detail.mime ?? "unknown"}). Use --force to override, or use "cloud asset download" for binary content.
|
|
7584
|
+
`
|
|
7585
|
+
);
|
|
7586
|
+
process.exit(1);
|
|
7587
|
+
}
|
|
7588
|
+
const { bytes, truncated, totalSize } = await fetchAssetBytes(
|
|
7589
|
+
client,
|
|
7590
|
+
assetId,
|
|
7591
|
+
opts.offset,
|
|
7592
|
+
opts.length
|
|
7593
|
+
);
|
|
7594
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
7595
|
+
process.stdout.write(text);
|
|
7596
|
+
if (text.length > 0 && !text.endsWith("\n")) process.stdout.write("\n");
|
|
7597
|
+
const start = opts.offset ?? 0;
|
|
7598
|
+
const end = start + bytes.byteLength;
|
|
7599
|
+
const range = `${start}..${end}`;
|
|
7600
|
+
process.stderr.write(
|
|
7601
|
+
`asset=${detail.id} hash=${detail.contentHash} range=${range} bytes=${bytes.byteLength} truncated=${truncated}${totalSize != null ? ` total=${totalSize}` : ""}
|
|
7602
|
+
`
|
|
7603
|
+
);
|
|
7604
|
+
} catch (err) {
|
|
7605
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
7606
|
+
`);
|
|
7607
|
+
process.exit(1);
|
|
7608
|
+
}
|
|
7609
|
+
});
|
|
7610
|
+
asset.command("sync").description("Refresh the local cache index by re-listing the workspace").option("--workspace-id <id>", "workspace id (defaults to PRISMER_WORKSPACE_ID env)").option("-n, --limit <n>", "maximum results to walk", "200").option("--json", "output raw JSON response").action(async (opts) => {
|
|
7611
|
+
const wsId = requireWorkspaceId(opts.workspaceId);
|
|
7612
|
+
const client = getIMClient2();
|
|
7613
|
+
try {
|
|
7614
|
+
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 200, 1), 200);
|
|
7615
|
+
const res = await client.im.assets.list({ workspaceId: wsId, limit });
|
|
7616
|
+
if (!res.ok) {
|
|
7617
|
+
if (opts.json) process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
7618
|
+
else process.stderr.write(`Error: ${res.error?.message || "sync failed"}
|
|
7619
|
+
`);
|
|
7620
|
+
process.exit(1);
|
|
7621
|
+
}
|
|
7622
|
+
const count = (res.data ?? []).length;
|
|
7623
|
+
if (opts.json) {
|
|
7624
|
+
process.stdout.write(JSON.stringify({ ok: true, data: { workspaceId: wsId, count } }, null, 2) + "\n");
|
|
7625
|
+
return;
|
|
7626
|
+
}
|
|
7627
|
+
process.stdout.write(`Synced ${count} asset(s) for workspace ${wsId}.
|
|
7628
|
+
`);
|
|
7629
|
+
process.stdout.write(
|
|
7630
|
+
"Note: asset bytes are fetched lazily on first read; this command refreshes metadata only.\n"
|
|
7631
|
+
);
|
|
7632
|
+
} catch (err) {
|
|
7633
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
7634
|
+
`);
|
|
7635
|
+
process.exit(1);
|
|
7636
|
+
}
|
|
7637
|
+
});
|
|
7638
|
+
}
|
|
7639
|
+
function parseIntOpt(value) {
|
|
7640
|
+
const n = parseInt(value, 10);
|
|
7641
|
+
if (Number.isNaN(n) || n < 0) {
|
|
7642
|
+
throw new Error(`expected non-negative integer, got "${value}"`);
|
|
7643
|
+
}
|
|
7644
|
+
return n;
|
|
7645
|
+
}
|
|
7646
|
+
function describeRange(offset, length, actual) {
|
|
7647
|
+
if (offset == null && length == null) return null;
|
|
7648
|
+
const start = offset ?? 0;
|
|
7649
|
+
const end = start + (actual ?? length ?? 0);
|
|
7650
|
+
return `range=${start}..${end}`;
|
|
7651
|
+
}
|
|
7652
|
+
async function fetchAssetBytes(client, assetId, offset, length) {
|
|
7653
|
+
const url = client.im.assets.url(assetId);
|
|
7654
|
+
const headers = {};
|
|
7655
|
+
if (offset != null || length != null) {
|
|
7656
|
+
const start = offset ?? 0;
|
|
7657
|
+
const end = length != null ? start + length - 1 : "";
|
|
7658
|
+
headers["Range"] = `bytes=${start}-${end}`;
|
|
7659
|
+
}
|
|
7660
|
+
const resp = await client.fetchAuthed(url, { method: "GET", headers });
|
|
7661
|
+
if (!resp.ok && resp.status !== 206) {
|
|
7662
|
+
let detail = "";
|
|
7663
|
+
try {
|
|
7664
|
+
detail = await resp.text();
|
|
7665
|
+
} catch {
|
|
7666
|
+
}
|
|
7667
|
+
throw new Error(`Asset fetch failed (${resp.status}): ${detail || resp.statusText}`);
|
|
7668
|
+
}
|
|
7669
|
+
const ab = await resp.arrayBuffer();
|
|
7670
|
+
const bytes = new Uint8Array(ab);
|
|
7671
|
+
let totalSize = null;
|
|
7672
|
+
const cr = resp.headers.get("content-range");
|
|
7673
|
+
if (cr) {
|
|
7674
|
+
const m = cr.match(/\/(\d+)$/);
|
|
7675
|
+
if (m) totalSize = Number(m[1]);
|
|
7676
|
+
} else {
|
|
7677
|
+
const cl = resp.headers.get("content-length");
|
|
7678
|
+
if (cl) totalSize = Number(cl);
|
|
7679
|
+
}
|
|
7680
|
+
let truncated = false;
|
|
7681
|
+
if (length != null) {
|
|
7682
|
+
if (totalSize != null) {
|
|
7683
|
+
const start = offset ?? 0;
|
|
7684
|
+
truncated = start + bytes.byteLength < totalSize;
|
|
7685
|
+
} else {
|
|
7686
|
+
truncated = bytes.byteLength >= length;
|
|
7687
|
+
}
|
|
7688
|
+
} else if (offset != null && totalSize != null) {
|
|
7689
|
+
truncated = offset + bytes.byteLength < totalSize;
|
|
7690
|
+
}
|
|
7691
|
+
return { bytes, truncated, totalSize };
|
|
7692
|
+
}
|
|
7693
|
+
|
|
7694
|
+
// src/commands/approval.ts
|
|
7695
|
+
function resolveWorkspaceId2(flag) {
|
|
7696
|
+
if (flag) return flag;
|
|
7697
|
+
if (typeof process !== "undefined" && process.env?.PRISMER_WORKSPACE_ID) {
|
|
7698
|
+
return process.env.PRISMER_WORKSPACE_ID;
|
|
7699
|
+
}
|
|
7700
|
+
return void 0;
|
|
7701
|
+
}
|
|
7702
|
+
function register12(parent, getIMClient2, _getAPIClient) {
|
|
7703
|
+
const approval = parent.command("approval").description("Submit and manage human approval requests");
|
|
7704
|
+
approval.command("request-human").description("Submit a human approval request and stop the current turn").requiredOption("--action <text>", "one-sentence summary of the gated action").requiredOption("--context <text>", "multi-sentence framing for the human").requiredOption("--risk <text>", "what breaks if approved wrongly; what is reversible").option("--options <opt...>", "explicit choice values (defaults to approve/reject if omitted)").option("--task-id <id>", "task to resume when the human decides").option("--conversation-id <id>", "conversation context for the request").option("--workspace-id <id>", "workspace id (defaults to PRISMER_WORKSPACE_ID env)").option("--category <category>", "approval category label", "human-approval").option("--expires-in <seconds>", "expiration window in seconds (default 24h)", parseIntOpt2).option("--json", "output raw JSON response").action(async (opts) => {
|
|
7705
|
+
if (!opts.conversationId && !opts.taskId) {
|
|
7706
|
+
process.stderr.write(
|
|
7707
|
+
"Error: --conversation-id or --task-id is required (human-approval needs an anchor).\n"
|
|
7708
|
+
);
|
|
7709
|
+
process.exit(1);
|
|
7710
|
+
}
|
|
7711
|
+
const workspaceId = resolveWorkspaceId2(opts.workspaceId);
|
|
7712
|
+
const composedContext = `${opts.context}
|
|
7713
|
+
|
|
7714
|
+
Risk: ${opts.risk}`;
|
|
7715
|
+
const optionList = opts.options && opts.options.length > 0 ? opts.options.map((v) => ({ value: v, label: v })) : [
|
|
7716
|
+
{ value: "approve", label: "Approve" },
|
|
7717
|
+
{ value: "reject", label: "Reject" }
|
|
7718
|
+
];
|
|
7719
|
+
const metadata = {
|
|
7720
|
+
risk: opts.risk,
|
|
7721
|
+
source: "cli:prismer-approval-request-human"
|
|
7722
|
+
};
|
|
7723
|
+
const body = {
|
|
7724
|
+
category: opts.category,
|
|
7725
|
+
title: opts.action,
|
|
7726
|
+
context: composedContext,
|
|
7727
|
+
options: optionList,
|
|
7728
|
+
metadata
|
|
7729
|
+
};
|
|
7730
|
+
if (workspaceId) body.workspaceId = workspaceId;
|
|
7731
|
+
if (opts.conversationId) body.conversationId = opts.conversationId;
|
|
7732
|
+
if (opts.taskId) body.taskId = opts.taskId;
|
|
7733
|
+
if (opts.expiresIn) body.expiresInSeconds = opts.expiresIn;
|
|
7734
|
+
try {
|
|
7735
|
+
const client = getIMClient2();
|
|
7736
|
+
const res = await client.im.request(
|
|
7737
|
+
"POST",
|
|
7738
|
+
"/api/im/approvals",
|
|
7739
|
+
body
|
|
7740
|
+
);
|
|
7741
|
+
if (opts.json) {
|
|
7742
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
7743
|
+
}
|
|
7744
|
+
if (!res.ok) {
|
|
7745
|
+
if (!opts.json) {
|
|
7746
|
+
process.stderr.write(`Error: ${res.error?.message || "approval submission failed"}
|
|
7747
|
+
`);
|
|
7748
|
+
}
|
|
7749
|
+
process.exit(1);
|
|
7750
|
+
}
|
|
7751
|
+
const data = res.data;
|
|
7752
|
+
const summary = {
|
|
7753
|
+
approvalId: data.id,
|
|
7754
|
+
status: data.status ?? "pending",
|
|
7755
|
+
expiresAt: data.expiresAt ?? null
|
|
7756
|
+
};
|
|
7757
|
+
if (!opts.json) {
|
|
7758
|
+
process.stdout.write(JSON.stringify(summary) + "\n");
|
|
7759
|
+
}
|
|
7760
|
+
process.stderr.write(
|
|
7761
|
+
`Submitted approval request ${data.id}. Stopping this turn.
|
|
7762
|
+
`
|
|
7763
|
+
);
|
|
7764
|
+
} catch (err) {
|
|
7765
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
7766
|
+
`);
|
|
7767
|
+
process.exit(1);
|
|
7768
|
+
}
|
|
7769
|
+
});
|
|
7770
|
+
}
|
|
7771
|
+
function parseIntOpt2(value) {
|
|
7772
|
+
const n = parseInt(value, 10);
|
|
7773
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
7774
|
+
throw new Error(`expected positive integer, got "${value}"`);
|
|
7775
|
+
}
|
|
7776
|
+
return n;
|
|
7777
|
+
}
|
|
7778
|
+
|
|
7779
|
+
// src/daemon.ts
|
|
7780
|
+
var fs3 = __toESM(require("fs"));
|
|
7781
|
+
var path3 = __toESM(require("path"));
|
|
6298
7782
|
var import_path = require("path");
|
|
6299
7783
|
var os = __toESM(require("os"));
|
|
6300
7784
|
var import_os = require("os");
|
|
@@ -6302,22 +7786,22 @@ var http = __toESM(require("http"));
|
|
|
6302
7786
|
var import_http = require("http");
|
|
6303
7787
|
var import_child_process = require("child_process");
|
|
6304
7788
|
var TOML = __toESM(require("@iarna/toml"));
|
|
6305
|
-
var CONFIG_DIR =
|
|
6306
|
-
var CONFIG_PATH =
|
|
6307
|
-
var PID_PATH =
|
|
6308
|
-
var PORT_PATH =
|
|
6309
|
-
var CACHE_DIR =
|
|
6310
|
-
var EVOLUTION_CACHE_PATH =
|
|
6311
|
-
var OUTBOX_PATH =
|
|
7789
|
+
var CONFIG_DIR = path3.join(os.homedir(), ".prismer");
|
|
7790
|
+
var CONFIG_PATH = path3.join(CONFIG_DIR, "config.toml");
|
|
7791
|
+
var PID_PATH = path3.join(CONFIG_DIR, "daemon.pid");
|
|
7792
|
+
var PORT_PATH = path3.join(CONFIG_DIR, "daemon.port");
|
|
7793
|
+
var CACHE_DIR = path3.join(CONFIG_DIR, "cache");
|
|
7794
|
+
var EVOLUTION_CACHE_PATH = path3.join(CACHE_DIR, "evolution.json");
|
|
7795
|
+
var OUTBOX_PATH = path3.join(CACHE_DIR, "outbox.json");
|
|
6312
7796
|
var SYNC_INTERVAL_MS = 6e4;
|
|
6313
7797
|
var FLUSH_INTERVAL_MS = 3e4;
|
|
6314
7798
|
var API_TIMEOUT_MS = 1e4;
|
|
6315
7799
|
var EVENTS_FILE = (0, import_path.join)(CACHE_DIR, "events.json");
|
|
6316
7800
|
var MAX_EVENTS = 1e3;
|
|
6317
7801
|
function loadConfig() {
|
|
6318
|
-
if (!
|
|
7802
|
+
if (!fs3.existsSync(CONFIG_PATH)) return null;
|
|
6319
7803
|
try {
|
|
6320
|
-
const raw =
|
|
7804
|
+
const raw = fs3.readFileSync(CONFIG_PATH, "utf-8");
|
|
6321
7805
|
const parsed = TOML.parse(raw);
|
|
6322
7806
|
const apiKey = parsed?.default?.api_key || "";
|
|
6323
7807
|
const baseUrl = parsed?.default?.base_url || "https://prismer.cloud";
|
|
@@ -6328,13 +7812,13 @@ function loadConfig() {
|
|
|
6328
7812
|
}
|
|
6329
7813
|
}
|
|
6330
7814
|
function ensureCacheDir() {
|
|
6331
|
-
if (!
|
|
6332
|
-
|
|
7815
|
+
if (!fs3.existsSync(CACHE_DIR)) {
|
|
7816
|
+
fs3.mkdirSync(CACHE_DIR, { recursive: true });
|
|
6333
7817
|
}
|
|
6334
7818
|
}
|
|
6335
7819
|
function loadEvents() {
|
|
6336
7820
|
try {
|
|
6337
|
-
return JSON.parse(
|
|
7821
|
+
return JSON.parse(fs3.readFileSync(EVENTS_FILE, "utf-8"));
|
|
6338
7822
|
} catch {
|
|
6339
7823
|
return [];
|
|
6340
7824
|
}
|
|
@@ -6343,7 +7827,7 @@ function appendEvent(event) {
|
|
|
6343
7827
|
const events = loadEvents();
|
|
6344
7828
|
events.push(event);
|
|
6345
7829
|
if (events.length > MAX_EVENTS) events.splice(0, events.length - MAX_EVENTS);
|
|
6346
|
-
|
|
7830
|
+
fs3.writeFileSync(EVENTS_FILE, JSON.stringify(events), { encoding: "utf-8", mode: 384 });
|
|
6347
7831
|
}
|
|
6348
7832
|
function emitSyncEvent(genesCount) {
|
|
6349
7833
|
if (genesCount > 0) {
|
|
@@ -6358,9 +7842,9 @@ function emitSyncEvent(genesCount) {
|
|
|
6358
7842
|
}
|
|
6359
7843
|
}
|
|
6360
7844
|
function readPid() {
|
|
6361
|
-
if (!
|
|
7845
|
+
if (!fs3.existsSync(PID_PATH)) return null;
|
|
6362
7846
|
try {
|
|
6363
|
-
const raw =
|
|
7847
|
+
const raw = fs3.readFileSync(PID_PATH, "utf-8").trim();
|
|
6364
7848
|
const pid = parseInt(raw, 10);
|
|
6365
7849
|
return isNaN(pid) ? null : pid;
|
|
6366
7850
|
} catch {
|
|
@@ -6368,9 +7852,9 @@ function readPid() {
|
|
|
6368
7852
|
}
|
|
6369
7853
|
}
|
|
6370
7854
|
function readPort() {
|
|
6371
|
-
if (!
|
|
7855
|
+
if (!fs3.existsSync(PORT_PATH)) return null;
|
|
6372
7856
|
try {
|
|
6373
|
-
const raw =
|
|
7857
|
+
const raw = fs3.readFileSync(PORT_PATH, "utf-8").trim();
|
|
6374
7858
|
const port = parseInt(raw, 10);
|
|
6375
7859
|
return isNaN(port) ? null : port;
|
|
6376
7860
|
} catch {
|
|
@@ -6387,24 +7871,24 @@ function isProcessRunning(pid) {
|
|
|
6387
7871
|
}
|
|
6388
7872
|
function writePid(pid) {
|
|
6389
7873
|
ensureCacheDir();
|
|
6390
|
-
if (!
|
|
6391
|
-
|
|
7874
|
+
if (!fs3.existsSync(CONFIG_DIR)) {
|
|
7875
|
+
fs3.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
6392
7876
|
}
|
|
6393
|
-
|
|
7877
|
+
fs3.writeFileSync(PID_PATH, String(pid), { encoding: "utf-8", mode: 384 });
|
|
6394
7878
|
}
|
|
6395
7879
|
function writePort(port) {
|
|
6396
|
-
if (!
|
|
6397
|
-
|
|
7880
|
+
if (!fs3.existsSync(CONFIG_DIR)) {
|
|
7881
|
+
fs3.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
6398
7882
|
}
|
|
6399
|
-
|
|
7883
|
+
fs3.writeFileSync(PORT_PATH, String(port), { encoding: "utf-8", mode: 384 });
|
|
6400
7884
|
}
|
|
6401
7885
|
function cleanupPidFiles() {
|
|
6402
7886
|
try {
|
|
6403
|
-
if (
|
|
7887
|
+
if (fs3.existsSync(PID_PATH)) fs3.unlinkSync(PID_PATH);
|
|
6404
7888
|
} catch {
|
|
6405
7889
|
}
|
|
6406
7890
|
try {
|
|
6407
|
-
if (
|
|
7891
|
+
if (fs3.existsSync(PORT_PATH)) fs3.unlinkSync(PORT_PATH);
|
|
6408
7892
|
} catch {
|
|
6409
7893
|
}
|
|
6410
7894
|
}
|
|
@@ -6420,16 +7904,16 @@ async function fetchWithTimeout(url, options, timeoutMs = API_TIMEOUT_MS) {
|
|
|
6420
7904
|
async function runDaemonProcess() {
|
|
6421
7905
|
const cfg = loadConfig();
|
|
6422
7906
|
if (!cfg) {
|
|
6423
|
-
process.stderr.write('[
|
|
7907
|
+
process.stderr.write('[cloud-daemon] No config found. Run "cloud setup" first.\n');
|
|
6424
7908
|
process.exit(1);
|
|
6425
7909
|
}
|
|
6426
7910
|
ensureCacheDir();
|
|
6427
7911
|
let lastSync = 0;
|
|
6428
7912
|
let syncCount = 0;
|
|
6429
7913
|
let evolutionCursor = 0;
|
|
6430
|
-
if (
|
|
7914
|
+
if (fs3.existsSync(EVOLUTION_CACHE_PATH)) {
|
|
6431
7915
|
try {
|
|
6432
|
-
const cached = JSON.parse(
|
|
7916
|
+
const cached = JSON.parse(fs3.readFileSync(EVOLUTION_CACHE_PATH, "utf-8"));
|
|
6433
7917
|
if (typeof cached?.cursor === "number") evolutionCursor = cached.cursor;
|
|
6434
7918
|
} catch {
|
|
6435
7919
|
}
|
|
@@ -6437,9 +7921,9 @@ async function runDaemonProcess() {
|
|
|
6437
7921
|
const server = (0, import_http.createServer)((req, res) => {
|
|
6438
7922
|
if (req.method === "GET" && req.url === "/health") {
|
|
6439
7923
|
let outboxSize = 0;
|
|
6440
|
-
if (
|
|
7924
|
+
if (fs3.existsSync(OUTBOX_PATH)) {
|
|
6441
7925
|
try {
|
|
6442
|
-
const entries = JSON.parse(
|
|
7926
|
+
const entries = JSON.parse(fs3.readFileSync(OUTBOX_PATH, "utf-8"));
|
|
6443
7927
|
if (Array.isArray(entries)) outboxSize = entries.length;
|
|
6444
7928
|
} catch {
|
|
6445
7929
|
}
|
|
@@ -6502,7 +7986,7 @@ async function runDaemonProcess() {
|
|
|
6502
7986
|
}
|
|
6503
7987
|
ensureCacheDir();
|
|
6504
7988
|
const pulled = data?.data || data;
|
|
6505
|
-
|
|
7989
|
+
fs3.writeFileSync(
|
|
6506
7990
|
EVOLUTION_CACHE_PATH,
|
|
6507
7991
|
JSON.stringify({ cursor: evolutionCursor, lastSync, data: pulled }, null, 2),
|
|
6508
7992
|
{ encoding: "utf-8", mode: 384 }
|
|
@@ -6513,10 +7997,10 @@ async function runDaemonProcess() {
|
|
|
6513
7997
|
}
|
|
6514
7998
|
};
|
|
6515
7999
|
const doOutboxFlush = async () => {
|
|
6516
|
-
if (!
|
|
8000
|
+
if (!fs3.existsSync(OUTBOX_PATH)) return;
|
|
6517
8001
|
let entries = [];
|
|
6518
8002
|
try {
|
|
6519
|
-
entries = JSON.parse(
|
|
8003
|
+
entries = JSON.parse(fs3.readFileSync(OUTBOX_PATH, "utf-8"));
|
|
6520
8004
|
if (!Array.isArray(entries) || entries.length === 0) return;
|
|
6521
8005
|
} catch {
|
|
6522
8006
|
return;
|
|
@@ -6537,7 +8021,7 @@ async function runDaemonProcess() {
|
|
|
6537
8021
|
}
|
|
6538
8022
|
);
|
|
6539
8023
|
if (res.ok) {
|
|
6540
|
-
|
|
8024
|
+
fs3.writeFileSync(OUTBOX_PATH, "[]", { encoding: "utf-8", mode: 384 });
|
|
6541
8025
|
}
|
|
6542
8026
|
} catch {
|
|
6543
8027
|
}
|
|
@@ -6567,7 +8051,7 @@ async function startDaemon() {
|
|
|
6567
8051
|
cleanupPidFiles();
|
|
6568
8052
|
const cfg = loadConfig();
|
|
6569
8053
|
if (!cfg) {
|
|
6570
|
-
console.error('No API key found. Run "
|
|
8054
|
+
console.error('No API key found. Run "cloud setup" first.');
|
|
6571
8055
|
process.exit(1);
|
|
6572
8056
|
}
|
|
6573
8057
|
if (process.env["PRISMER_DAEMON"] === "1") {
|
|
@@ -6655,7 +8139,7 @@ function resolveNpxPath() {
|
|
|
6655
8139
|
} catch {
|
|
6656
8140
|
for (const p of ["/usr/local/bin/npx", "/opt/homebrew/bin/npx", `${(0, import_os.homedir)()}/.nvm/current/bin/npx`]) {
|
|
6657
8141
|
try {
|
|
6658
|
-
|
|
8142
|
+
fs3.accessSync(p);
|
|
6659
8143
|
return p;
|
|
6660
8144
|
} catch {
|
|
6661
8145
|
}
|
|
@@ -6697,8 +8181,8 @@ function installLaunchd() {
|
|
|
6697
8181
|
<string>${(0, import_path.join)((0, import_os.homedir)(), ".prismer", "daemon.stderr.log")}</string>
|
|
6698
8182
|
</dict>
|
|
6699
8183
|
</plist>`;
|
|
6700
|
-
|
|
6701
|
-
|
|
8184
|
+
fs3.mkdirSync((0, import_path.dirname)(plistPath), { recursive: true });
|
|
8185
|
+
fs3.writeFileSync(plistPath, plist, { mode: 384 });
|
|
6702
8186
|
try {
|
|
6703
8187
|
(0, import_child_process.execSync)(`launchctl load ${plistPath}`, { stdio: "pipe" });
|
|
6704
8188
|
console.log("[prismer] Daemon service installed and started (launchd)");
|
|
@@ -6714,7 +8198,7 @@ function uninstallLaunchd() {
|
|
|
6714
8198
|
} catch {
|
|
6715
8199
|
}
|
|
6716
8200
|
try {
|
|
6717
|
-
|
|
8201
|
+
fs3.unlinkSync(plistPath);
|
|
6718
8202
|
} catch {
|
|
6719
8203
|
}
|
|
6720
8204
|
console.log("[prismer] Daemon service uninstalled (launchd)");
|
|
@@ -6739,8 +8223,8 @@ RestartSec=10
|
|
|
6739
8223
|
[Install]
|
|
6740
8224
|
WantedBy=default.target
|
|
6741
8225
|
`;
|
|
6742
|
-
|
|
6743
|
-
|
|
8226
|
+
fs3.mkdirSync(serviceDir, { recursive: true });
|
|
8227
|
+
fs3.writeFileSync(servicePath, unit, { mode: 420 });
|
|
6744
8228
|
try {
|
|
6745
8229
|
(0, import_child_process.execSync)("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
6746
8230
|
(0, import_child_process.execSync)("systemctl --user enable prismer-daemon", { stdio: "pipe" });
|
|
@@ -6763,7 +8247,7 @@ function uninstallSystemd() {
|
|
|
6763
8247
|
}
|
|
6764
8248
|
const servicePath = (0, import_path.join)((0, import_os.homedir)(), ".config", "systemd", "user", "prismer-daemon.service");
|
|
6765
8249
|
try {
|
|
6766
|
-
|
|
8250
|
+
fs3.unlinkSync(servicePath);
|
|
6767
8251
|
} catch {
|
|
6768
8252
|
}
|
|
6769
8253
|
try {
|
|
@@ -6779,7 +8263,7 @@ function installDaemonService() {
|
|
|
6779
8263
|
} else if (platform === "linux") {
|
|
6780
8264
|
installSystemd();
|
|
6781
8265
|
} else {
|
|
6782
|
-
console.log(`Daemon auto-start not supported on ${platform}. Use:
|
|
8266
|
+
console.log(`Daemon auto-start not supported on ${platform}. Use: cloud daemon start`);
|
|
6783
8267
|
}
|
|
6784
8268
|
}
|
|
6785
8269
|
function uninstallDaemonService() {
|
|
@@ -6803,26 +8287,39 @@ if (process.env["PRISMER_DAEMON"] === "1") {
|
|
|
6803
8287
|
// src/cli.ts
|
|
6804
8288
|
var cliVersion = "1.7.2";
|
|
6805
8289
|
try {
|
|
6806
|
-
const pkgPath =
|
|
6807
|
-
const pkg = JSON.parse(
|
|
8290
|
+
const pkgPath = path4.join(__dirname, "..", "package.json");
|
|
8291
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
|
|
6808
8292
|
cliVersion = pkg.version || cliVersion;
|
|
6809
8293
|
} catch {
|
|
6810
8294
|
}
|
|
6811
|
-
var CONFIG_DIR2 =
|
|
6812
|
-
var CONFIG_PATH2 =
|
|
8295
|
+
var CONFIG_DIR2 = process.env.PRISMER_HOME ? path4.resolve(process.env.PRISMER_HOME) : path4.join(os2.homedir(), ".prismer");
|
|
8296
|
+
var CONFIG_PATH2 = path4.join(CONFIG_DIR2, "config.toml");
|
|
6813
8297
|
function ensureConfigDir() {
|
|
6814
|
-
if (!
|
|
6815
|
-
|
|
8298
|
+
if (!fs4.existsSync(CONFIG_DIR2)) {
|
|
8299
|
+
fs4.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
6816
8300
|
}
|
|
6817
8301
|
}
|
|
6818
8302
|
function readConfig() {
|
|
6819
|
-
if (!
|
|
6820
|
-
const raw =
|
|
6821
|
-
|
|
8303
|
+
if (!fs4.existsSync(CONFIG_PATH2)) return {};
|
|
8304
|
+
const raw = fs4.readFileSync(CONFIG_PATH2, "utf-8");
|
|
8305
|
+
const parsed = TOML2.parse(raw);
|
|
8306
|
+
const flatApiKey = parsed.api_key;
|
|
8307
|
+
const flatBaseUrl = parsed.cloud_api_base ?? parsed.base_url;
|
|
8308
|
+
const flatEnv = parsed.environment;
|
|
8309
|
+
if (flatApiKey || flatBaseUrl || flatEnv) {
|
|
8310
|
+
parsed.default = {
|
|
8311
|
+
...parsed.default ?? {},
|
|
8312
|
+
// Explicit [default].api_key wins; flat is the fallback (runtime-written).
|
|
8313
|
+
api_key: parsed.default?.api_key ?? flatApiKey,
|
|
8314
|
+
base_url: parsed.default?.base_url ?? flatBaseUrl,
|
|
8315
|
+
environment: parsed.default?.environment ?? flatEnv
|
|
8316
|
+
};
|
|
8317
|
+
}
|
|
8318
|
+
return parsed;
|
|
6822
8319
|
}
|
|
6823
8320
|
function writeConfig(config) {
|
|
6824
8321
|
ensureConfigDir();
|
|
6825
|
-
|
|
8322
|
+
fs4.writeFileSync(CONFIG_PATH2, TOML2.stringify(config), { encoding: "utf-8", mode: 384 });
|
|
6826
8323
|
}
|
|
6827
8324
|
function setNestedValue(obj, dotPath, value) {
|
|
6828
8325
|
const parts = dotPath.split(".");
|
|
@@ -6836,20 +8333,24 @@ function setNestedValue(obj, dotPath, value) {
|
|
|
6836
8333
|
}
|
|
6837
8334
|
function getIMClient() {
|
|
6838
8335
|
const cfg = readConfig();
|
|
6839
|
-
const token = cfg?.auth?.im_token;
|
|
6840
|
-
if (!token) {
|
|
6841
|
-
error('No IM token. Run "prismer setup --agent" or "prismer register <username>" first.');
|
|
6842
|
-
process.exit(1);
|
|
6843
|
-
}
|
|
6844
8336
|
const env = cfg?.default?.environment || "production";
|
|
6845
8337
|
const baseUrl = cfg?.default?.base_url || "";
|
|
6846
|
-
|
|
8338
|
+
const imToken = cfg?.auth?.im_token;
|
|
8339
|
+
if (imToken) {
|
|
8340
|
+
return new PrismerClient({ apiKey: imToken, environment: env, ...baseUrl ? { baseUrl } : {} });
|
|
8341
|
+
}
|
|
8342
|
+
const apiKey = cfg?.default?.api_key;
|
|
8343
|
+
if (!apiKey) {
|
|
8344
|
+
errorLine('No credentials. Run "cloud setup" first (or "cloud setup --agent" / "cloud register <username>" for IM-JWT path).');
|
|
8345
|
+
process.exit(1);
|
|
8346
|
+
}
|
|
8347
|
+
return new PrismerClient({ apiKey, environment: env, ...baseUrl ? { baseUrl } : {} });
|
|
6847
8348
|
}
|
|
6848
8349
|
function getAPIClient() {
|
|
6849
8350
|
const cfg = readConfig();
|
|
6850
8351
|
const apiKey = cfg?.default?.api_key;
|
|
6851
8352
|
if (!apiKey) {
|
|
6852
|
-
|
|
8353
|
+
errorLine('No API key. Run "cloud setup" to sign in and get your key.');
|
|
6853
8354
|
process.exit(1);
|
|
6854
8355
|
}
|
|
6855
8356
|
const env = cfg?.default?.environment || "production";
|
|
@@ -6857,15 +8358,15 @@ function getAPIClient() {
|
|
|
6857
8358
|
return new PrismerClient({ apiKey, environment: env, ...baseUrl ? { baseUrl } : {} });
|
|
6858
8359
|
}
|
|
6859
8360
|
var program = new import_commander.Command();
|
|
6860
|
-
program.name("
|
|
8361
|
+
program.name("cloud").description("Prismer Cloud SDK CLI").version(cliVersion);
|
|
6861
8362
|
async function verifyAndSaveKey(config, apiKey) {
|
|
6862
8363
|
if (!apiKey) {
|
|
6863
|
-
|
|
8364
|
+
errorLine("No key provided.");
|
|
6864
8365
|
process.exit(1);
|
|
6865
8366
|
}
|
|
6866
8367
|
if (!apiKey.startsWith("sk-prismer-")) {
|
|
6867
|
-
|
|
6868
|
-
|
|
8368
|
+
errorLine("Invalid key format. API keys start with sk-prismer-");
|
|
8369
|
+
dim(" Get your key at: https://prismer.cloud/setup");
|
|
6869
8370
|
process.exit(1);
|
|
6870
8371
|
}
|
|
6871
8372
|
const baseUrl = config.default?.base_url || "https://prismer.cloud";
|
|
@@ -6874,8 +8375,8 @@ async function verifyAndSaveKey(config, apiKey) {
|
|
|
6874
8375
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
6875
8376
|
});
|
|
6876
8377
|
if (res.status === 401) {
|
|
6877
|
-
|
|
6878
|
-
|
|
8378
|
+
errorLine("API key is invalid or expired.");
|
|
8379
|
+
dim(" Get a new key at: https://prismer.cloud/setup");
|
|
6879
8380
|
process.exit(1);
|
|
6880
8381
|
}
|
|
6881
8382
|
success("API key verified");
|
|
@@ -6892,7 +8393,7 @@ async function verifyAndSaveKey(config, apiKey) {
|
|
|
6892
8393
|
try {
|
|
6893
8394
|
installDaemonService();
|
|
6894
8395
|
} catch {
|
|
6895
|
-
|
|
8396
|
+
dim("Daemon auto-start setup skipped. Run manually: cloud daemon install");
|
|
6896
8397
|
}
|
|
6897
8398
|
}
|
|
6898
8399
|
function openBrowser(url) {
|
|
@@ -6919,8 +8420,8 @@ async function runSetup(opts, apiKey) {
|
|
|
6919
8420
|
const masked = config.default.api_key.slice(0, 12) + "..." + config.default.api_key.slice(-4);
|
|
6920
8421
|
success(`Already configured: ${masked}`);
|
|
6921
8422
|
console.log("");
|
|
6922
|
-
|
|
6923
|
-
|
|
8423
|
+
dim(" To reconfigure, run: cloud setup --force");
|
|
8424
|
+
dim(" To check status: cloud status");
|
|
6924
8425
|
return;
|
|
6925
8426
|
}
|
|
6926
8427
|
if (apiKey) {
|
|
@@ -6930,7 +8431,7 @@ async function runSetup(opts, apiKey) {
|
|
|
6930
8431
|
if (opts.agent) {
|
|
6931
8432
|
if (!opts.force && config.auth?.im_token) {
|
|
6932
8433
|
success("Already registered as agent (IM token exists).");
|
|
6933
|
-
|
|
8434
|
+
dim(" For API key access, run: cloud setup");
|
|
6934
8435
|
return;
|
|
6935
8436
|
}
|
|
6936
8437
|
const username = `agent-${Date.now().toString(36)}`;
|
|
@@ -6953,10 +8454,10 @@ async function runSetup(opts, apiKey) {
|
|
|
6953
8454
|
"User ID": config.auth.im_user_id || ""
|
|
6954
8455
|
});
|
|
6955
8456
|
console.log("");
|
|
6956
|
-
info("For full API access, sign in:
|
|
8457
|
+
info("For full API access, sign in: cloud setup");
|
|
6957
8458
|
} catch (err) {
|
|
6958
|
-
|
|
6959
|
-
|
|
8459
|
+
errorLine(`Agent registration failed: ${err.message}`);
|
|
8460
|
+
dim(" Try signing in instead: cloud setup");
|
|
6960
8461
|
process.exit(1);
|
|
6961
8462
|
}
|
|
6962
8463
|
return;
|
|
@@ -6964,7 +8465,7 @@ async function runSetup(opts, apiKey) {
|
|
|
6964
8465
|
if (opts.manual) {
|
|
6965
8466
|
const setupUrl = `${baseUrl}/setup?utm_source=cli&utm_medium=manual`;
|
|
6966
8467
|
info("Opening browser to sign in...");
|
|
6967
|
-
|
|
8468
|
+
dim(` ${setupUrl}`);
|
|
6968
8469
|
console.log("");
|
|
6969
8470
|
openBrowser(setupUrl);
|
|
6970
8471
|
info("After signing in, copy the API key from the page and paste it below.");
|
|
@@ -6974,7 +8475,7 @@ async function runSetup(opts, apiKey) {
|
|
|
6974
8475
|
rl.question("Paste your API key: ", (key) => {
|
|
6975
8476
|
rl.close();
|
|
6976
8477
|
verifyAndSaveKey(config, key.trim()).catch((err) => {
|
|
6977
|
-
|
|
8478
|
+
errorLine(`Setup failed: ${err.message}`);
|
|
6978
8479
|
process.exit(1);
|
|
6979
8480
|
});
|
|
6980
8481
|
});
|
|
@@ -7021,16 +8522,16 @@ async function runSetup(opts, apiKey) {
|
|
|
7021
8522
|
console.log("");
|
|
7022
8523
|
openBrowser(setupUrl);
|
|
7023
8524
|
info("Waiting for authentication...");
|
|
7024
|
-
|
|
7025
|
-
|
|
8525
|
+
dim(" (If the browser didn't open, visit this URL manually:)");
|
|
8526
|
+
dim(` ${setupUrl}`);
|
|
7026
8527
|
console.log("");
|
|
7027
8528
|
setTimeout(() => {
|
|
7028
8529
|
if (!resolved) {
|
|
7029
|
-
|
|
8530
|
+
errorLine("Timed out waiting for authentication (5 min).");
|
|
7030
8531
|
console.log("");
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
8532
|
+
dim(" Alternatives:");
|
|
8533
|
+
dim(" cloud setup --manual Paste key manually");
|
|
8534
|
+
dim(" cloud setup --agent Register as agent (free credits, no browser)");
|
|
7034
8535
|
server.close();
|
|
7035
8536
|
process.exit(1);
|
|
7036
8537
|
}
|
|
@@ -7040,8 +8541,8 @@ async function runSetup(opts, apiKey) {
|
|
|
7040
8541
|
program.command("setup [api-key]").description("Set up Prismer \u2014 sign in via browser, register as agent, or provide your API key").option("--manual", "Paste API key manually instead of browser auto-flow").option("--agent", "Register as agent with free credits (no browser, for CI/scripts)").option("--force", "Reconfigure even if already set up").action(async (apiKey, opts) => {
|
|
7041
8542
|
await runSetup(opts, apiKey);
|
|
7042
8543
|
});
|
|
7043
|
-
program.command("init [api-key]").description('Alias for "
|
|
7044
|
-
warn('"
|
|
8544
|
+
program.command("init [api-key]").description('Alias for "cloud setup" (deprecated, use setup instead)').option("--manual", "Paste API key manually").option("--agent", "Register as agent with free credits").option("--force", "Reconfigure even if already set up").action(async (apiKey, opts) => {
|
|
8545
|
+
warn('"cloud init" is deprecated. Use "cloud setup" instead.');
|
|
7045
8546
|
console.log("");
|
|
7046
8547
|
await runSetup(opts, apiKey);
|
|
7047
8548
|
});
|
|
@@ -7049,7 +8550,7 @@ program.command("register <username>").description("Register an IM identity and
|
|
|
7049
8550
|
const config = readConfig();
|
|
7050
8551
|
const apiKey = config.default?.api_key;
|
|
7051
8552
|
if (!apiKey) {
|
|
7052
|
-
|
|
8553
|
+
errorLine('No API key. Run "cloud setup" first.');
|
|
7053
8554
|
process.exit(1);
|
|
7054
8555
|
}
|
|
7055
8556
|
const client = new PrismerClient({
|
|
@@ -7069,7 +8570,7 @@ program.command("register <username>").description("Register an IM identity and
|
|
|
7069
8570
|
try {
|
|
7070
8571
|
const result = await client.im.account.register(registerOpts);
|
|
7071
8572
|
if (!result.ok || !result.data) {
|
|
7072
|
-
|
|
8573
|
+
errorLine(`Registration failed: ${result.error?.message || "Unknown error"}`);
|
|
7073
8574
|
process.exit(1);
|
|
7074
8575
|
}
|
|
7075
8576
|
const data = result.data;
|
|
@@ -7087,9 +8588,9 @@ program.command("register <username>").description("Register an IM identity and
|
|
|
7087
8588
|
"Role": data.role,
|
|
7088
8589
|
"New": String(data.isNew)
|
|
7089
8590
|
});
|
|
7090
|
-
|
|
8591
|
+
dim(" Token stored in ~/.prismer/config.toml");
|
|
7091
8592
|
} catch (err) {
|
|
7092
|
-
|
|
8593
|
+
errorLine(`Registration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
7093
8594
|
process.exit(1);
|
|
7094
8595
|
}
|
|
7095
8596
|
});
|
|
@@ -7146,16 +8647,16 @@ program.command("status").description("Show current config and live info").actio
|
|
|
7146
8647
|
warn(`Could not fetch live info: ${me.error?.message || "unknown error"}`);
|
|
7147
8648
|
}
|
|
7148
8649
|
} else {
|
|
7149
|
-
|
|
8650
|
+
dim(" IM Token: (not registered)");
|
|
7150
8651
|
}
|
|
7151
8652
|
});
|
|
7152
8653
|
var configCmd = program.command("config").description("Manage config file");
|
|
7153
8654
|
configCmd.command("show").description("Print config file").action(() => {
|
|
7154
|
-
if (!
|
|
7155
|
-
warn('No config file. Run "
|
|
8655
|
+
if (!fs4.existsSync(CONFIG_PATH2)) {
|
|
8656
|
+
warn('No config file. Run "cloud setup" to create one.');
|
|
7156
8657
|
return;
|
|
7157
8658
|
}
|
|
7158
|
-
console.log(
|
|
8659
|
+
console.log(fs4.readFileSync(CONFIG_PATH2, "utf-8"));
|
|
7159
8660
|
});
|
|
7160
8661
|
configCmd.command("set <key> <value>").description("Set a config value (e.g. default.base_url)").action((key, value) => {
|
|
7161
8662
|
const config = readConfig();
|
|
@@ -7172,7 +8673,7 @@ tokenCmd.command("refresh").description("Refresh IM JWT token").option("--json",
|
|
|
7172
8673
|
return;
|
|
7173
8674
|
}
|
|
7174
8675
|
if (!res.ok) {
|
|
7175
|
-
|
|
8676
|
+
errorLine(`Token refresh failed: ${JSON.stringify(res.error)}`);
|
|
7176
8677
|
process.exit(1);
|
|
7177
8678
|
}
|
|
7178
8679
|
const data = res.data;
|
|
@@ -7197,12 +8698,44 @@ register7(program, getIMClient, getAPIClient);
|
|
|
7197
8698
|
register8(program, getIMClient, getAPIClient);
|
|
7198
8699
|
register9(program, getIMClient, getAPIClient);
|
|
7199
8700
|
register10(program, getIMClient, getAPIClient);
|
|
7200
|
-
program
|
|
8701
|
+
register11(program, getIMClient, getAPIClient);
|
|
8702
|
+
register12(program, getIMClient, getAPIClient);
|
|
8703
|
+
program.command("send").description("Send a direct message (shortcut for: im send)").argument("<user-id-or-username>", "Target user/agent IM user ID (or username with --by-username)").argument("<message>", "Message content").option("-t, --type <type>", "Message type: text, markdown, code, etc.", "text").option("--reply-to <id>", "Reply to a message ID").option("--conversation-id <id>", "Pin message to a specific conversation/session").option("--asset-id <id>", "Attach a previously uploaded asset (treats type as file)").option("--by-username", "Treat the first argument as a username; resolve to imUserId first").option("--json", "JSON output").action(async (target, message, opts) => {
|
|
7201
8704
|
const client = getIMClient();
|
|
8705
|
+
let userId = target;
|
|
8706
|
+
if (opts.byUsername) {
|
|
8707
|
+
const discoverRes = await client.im.contacts.discover();
|
|
8708
|
+
if (!discoverRes.ok || !Array.isArray(discoverRes.data)) {
|
|
8709
|
+
errorLine(`Could not resolve username "${target}" \u2014 discover failed.`);
|
|
8710
|
+
process.exit(1);
|
|
8711
|
+
}
|
|
8712
|
+
const needle = target.trim().toLowerCase().replace(/^@/, "");
|
|
8713
|
+
const match = discoverRes.data.find((u) => {
|
|
8714
|
+
const vals = [u.username, u.displayName, u.userId].map(
|
|
8715
|
+
(v) => typeof v === "string" ? v.trim().toLowerCase() : ""
|
|
8716
|
+
);
|
|
8717
|
+
return vals.includes(needle);
|
|
8718
|
+
});
|
|
8719
|
+
if (!match?.userId) {
|
|
8720
|
+
errorLine(`Could not resolve username "${target}" to an IM user.`);
|
|
8721
|
+
process.exit(1);
|
|
8722
|
+
}
|
|
8723
|
+
userId = match.userId;
|
|
8724
|
+
}
|
|
7202
8725
|
const sendOpts = {};
|
|
7203
8726
|
if (opts.type && opts.type !== "text") sendOpts.type = opts.type;
|
|
7204
8727
|
if (opts.replyTo) sendOpts.parentId = opts.replyTo;
|
|
8728
|
+
if (opts.assetId) {
|
|
8729
|
+
if (!opts.type || opts.type === "text") sendOpts.type = "file";
|
|
8730
|
+
sendOpts.attachments = [{ kind: "asset", assetId: opts.assetId, role: "attachment" }];
|
|
8731
|
+
}
|
|
8732
|
+
if (opts.conversationId) {
|
|
8733
|
+
sendOpts.metadata = { ...sendOpts.metadata ?? {}, conversationId: opts.conversationId };
|
|
8734
|
+
}
|
|
7205
8735
|
const res = await withSpinner("Sending message", async () => {
|
|
8736
|
+
if (opts.conversationId) {
|
|
8737
|
+
return client.im.messages.send(opts.conversationId, message, sendOpts);
|
|
8738
|
+
}
|
|
7206
8739
|
return client.im.direct.send(userId, message, sendOpts);
|
|
7207
8740
|
});
|
|
7208
8741
|
if (opts.json) {
|
|
@@ -7210,7 +8743,7 @@ program.command("send").description("Send a direct message (shortcut for: im sen
|
|
|
7210
8743
|
return;
|
|
7211
8744
|
}
|
|
7212
8745
|
if (!res.ok) {
|
|
7213
|
-
|
|
8746
|
+
errorLine(`Send failed: ${JSON.stringify(res.error)}`);
|
|
7214
8747
|
process.exit(1);
|
|
7215
8748
|
}
|
|
7216
8749
|
success(`Message sent (conversation: ${res.data?.conversationId})`);
|
|
@@ -7228,7 +8761,7 @@ program.command("load").description("Load URL(s) \u2192 compressed HQCC (shortcu
|
|
|
7228
8761
|
return;
|
|
7229
8762
|
}
|
|
7230
8763
|
if (!res.success) {
|
|
7231
|
-
|
|
8764
|
+
errorLine(res.error?.message || "Load failed");
|
|
7232
8765
|
process.exit(1);
|
|
7233
8766
|
}
|
|
7234
8767
|
const results = res.results || (res.result ? [res.result] : []);
|
|
@@ -7256,7 +8789,7 @@ program.command("search").description("Search web content (shortcut for: context
|
|
|
7256
8789
|
return;
|
|
7257
8790
|
}
|
|
7258
8791
|
if (!res.success) {
|
|
7259
|
-
|
|
8792
|
+
errorLine(res.error?.message || "Search failed");
|
|
7260
8793
|
process.exit(1);
|
|
7261
8794
|
}
|
|
7262
8795
|
const results = res.results || [];
|
|
@@ -7274,7 +8807,7 @@ program.command("search").description("Search web content (shortcut for: context
|
|
|
7274
8807
|
const r = results[i];
|
|
7275
8808
|
if (r.hqcc) {
|
|
7276
8809
|
console.log("");
|
|
7277
|
-
|
|
8810
|
+
dim(` ${i + 1}. ${r.hqcc.substring(0, 200)}`);
|
|
7278
8811
|
}
|
|
7279
8812
|
}
|
|
7280
8813
|
});
|
|
@@ -7288,7 +8821,7 @@ program.command("parse").description("Parse a document via OCR (shortcut for: pa
|
|
|
7288
8821
|
return;
|
|
7289
8822
|
}
|
|
7290
8823
|
if (!res.success) {
|
|
7291
|
-
|
|
8824
|
+
errorLine(res.error?.message || "Parse failed");
|
|
7292
8825
|
process.exit(1);
|
|
7293
8826
|
}
|
|
7294
8827
|
if (res.taskId) {
|
|
@@ -7297,7 +8830,7 @@ program.command("parse").description("Parse a document via OCR (shortcut for: pa
|
|
|
7297
8830
|
"Status": res.status || "processing"
|
|
7298
8831
|
});
|
|
7299
8832
|
console.log("");
|
|
7300
|
-
|
|
8833
|
+
dim(` Check: cloud parse-status ${res.taskId}`);
|
|
7301
8834
|
} else if (res.document) {
|
|
7302
8835
|
success("Parse complete");
|
|
7303
8836
|
const content = res.document.markdown || res.document.text || JSON.stringify(res.document, null, 2);
|
|
@@ -7327,27 +8860,50 @@ program.command("parse-result").description("Get parse result").argument("<task-
|
|
|
7327
8860
|
return;
|
|
7328
8861
|
}
|
|
7329
8862
|
if (!res.success) {
|
|
7330
|
-
|
|
8863
|
+
errorLine(res.error?.message || "Not ready");
|
|
7331
8864
|
process.exit(1);
|
|
7332
8865
|
}
|
|
7333
8866
|
success("Parse result ready");
|
|
7334
8867
|
const content = res.document?.markdown || res.document?.text || JSON.stringify(res.document, null, 2);
|
|
7335
8868
|
console.log(content);
|
|
7336
8869
|
});
|
|
7337
|
-
program.command("recall").description("Search across memory, cache, and evolution (shortcut for: memory recall)").argument("<query>", "Search query").option("--scope <scope>", "Scope: all, memory, cache, evolution", "all").option("-n, --limit <n>", "Max results", "10").option("--json", "JSON output").action(async (query, opts) => {
|
|
8870
|
+
program.command("recall").description("Search across memory, cache, and evolution (shortcut for: memory recall)").argument("<query>", "Search query").option("--scope <scope>", "Scope: all, memory, cache, evolution", "all").option("--layer <layer>", "Alias for --scope (memory | cache | evolution | all)").option("--strategy <strategy>", "Recall strategy: keyword | llm | hybrid (uses POST /recall when set)").option("-n, --limit <n>", "Max results", "10").option("--json", "JSON output").action(async (query, opts) => {
|
|
7338
8871
|
const client = getIMClient();
|
|
7339
|
-
|
|
7340
|
-
if (
|
|
7341
|
-
|
|
8872
|
+
let scope = opts.layer || opts.scope || "all";
|
|
8873
|
+
if (scope === "context") scope = "cache";
|
|
8874
|
+
const validStrategies = ["keyword", "llm", "hybrid"];
|
|
8875
|
+
if (opts.strategy && !validStrategies.includes(opts.strategy)) {
|
|
8876
|
+
errorLine(`Invalid --strategy "${opts.strategy}". Use one of: ${validStrategies.join(", ")}.`);
|
|
8877
|
+
process.exit(1);
|
|
8878
|
+
}
|
|
7342
8879
|
const res = await withSpinner(`Recalling: ${query}`, async () => {
|
|
7343
|
-
|
|
8880
|
+
if (opts.strategy) {
|
|
8881
|
+
return client.im.request(
|
|
8882
|
+
"POST",
|
|
8883
|
+
"/api/im/recall",
|
|
8884
|
+
{
|
|
8885
|
+
query,
|
|
8886
|
+
strategy: opts.strategy,
|
|
8887
|
+
scope,
|
|
8888
|
+
maxResults: opts.limit ? parseInt(opts.limit, 10) : void 0
|
|
8889
|
+
}
|
|
8890
|
+
);
|
|
8891
|
+
}
|
|
8892
|
+
const params = { q: query, scope };
|
|
8893
|
+
if (opts.limit) params.limit = String(opts.limit);
|
|
8894
|
+
return client.im.request(
|
|
8895
|
+
"GET",
|
|
8896
|
+
"/api/im/recall",
|
|
8897
|
+
void 0,
|
|
8898
|
+
params
|
|
8899
|
+
);
|
|
7344
8900
|
});
|
|
7345
8901
|
if (opts.json) {
|
|
7346
8902
|
console.log(JSON.stringify(res, null, 2));
|
|
7347
8903
|
return;
|
|
7348
8904
|
}
|
|
7349
8905
|
if (!res.ok) {
|
|
7350
|
-
|
|
8906
|
+
errorLine(`Recall failed: ${JSON.stringify(res.error)}`);
|
|
7351
8907
|
process.exit(1);
|
|
7352
8908
|
}
|
|
7353
8909
|
const data = res.data || [];
|
|
@@ -7356,22 +8912,27 @@ program.command("recall").description("Search across memory, cache, and evolutio
|
|
|
7356
8912
|
return;
|
|
7357
8913
|
}
|
|
7358
8914
|
const rows = data.map((item) => [
|
|
7359
|
-
(item.source || "").toUpperCase(),
|
|
7360
|
-
item.title || "?",
|
|
8915
|
+
(item.source || item.memoryType || "").toUpperCase(),
|
|
8916
|
+
item.title || item.path || "?",
|
|
7361
8917
|
(item.score || 0).toFixed(2)
|
|
7362
8918
|
]);
|
|
7363
8919
|
table(["Source", "Title", "Score"], rows);
|
|
7364
8920
|
for (const item of data) {
|
|
7365
|
-
|
|
7366
|
-
|
|
8921
|
+
const snippet = item.snippet || item.content;
|
|
8922
|
+
if (snippet) {
|
|
8923
|
+
dim(` ${String(snippet).substring(0, 200)}`);
|
|
7367
8924
|
}
|
|
7368
8925
|
}
|
|
7369
8926
|
});
|
|
7370
|
-
program.command("discover").description("Discover available agents (shortcut for: im discover)").option("--type <type>", "Filter by agent type").option("--capability <cap>", "Filter by capability").option("--json", "JSON output").action(async (opts) => {
|
|
8927
|
+
program.command("discover").description("Discover available agents (shortcut for: im discover)").option("--type <type>", "Filter by agent type").option("--capability <cap>", "Filter by capability").option("--online-only", "Only return agents currently online").option("--json", "JSON output").action(async (opts) => {
|
|
7371
8928
|
const client = getIMClient();
|
|
7372
8929
|
const discoverOpts = {};
|
|
7373
8930
|
if (opts.type) discoverOpts.type = opts.type;
|
|
7374
8931
|
if (opts.capability) discoverOpts.capability = opts.capability;
|
|
8932
|
+
if (opts.onlineOnly) {
|
|
8933
|
+
discoverOpts.status = "online";
|
|
8934
|
+
discoverOpts.onlineOnly = "true";
|
|
8935
|
+
}
|
|
7375
8936
|
const res = await withSpinner("Discovering agents", async () => {
|
|
7376
8937
|
return client.im.contacts.discover(discoverOpts);
|
|
7377
8938
|
});
|
|
@@ -7380,7 +8941,7 @@ program.command("discover").description("Discover available agents (shortcut for
|
|
|
7380
8941
|
return;
|
|
7381
8942
|
}
|
|
7382
8943
|
if (!res.ok) {
|
|
7383
|
-
|
|
8944
|
+
errorLine(`Discovery failed: ${JSON.stringify(res.error)}`);
|
|
7384
8945
|
process.exit(1);
|
|
7385
8946
|
}
|
|
7386
8947
|
const agents = res.data || [];
|
|
@@ -7414,12 +8975,18 @@ program.command("daemon <action>").description("Manage background sync daemon (s
|
|
|
7414
8975
|
uninstallDaemonService();
|
|
7415
8976
|
break;
|
|
7416
8977
|
default:
|
|
7417
|
-
|
|
8978
|
+
errorLine(`Unknown daemon action: ${action}. Use: start, stop, status, install, uninstall`);
|
|
7418
8979
|
process.exit(1);
|
|
7419
8980
|
}
|
|
7420
8981
|
});
|
|
7421
|
-
|
|
7422
|
-
|
|
8982
|
+
var _head = process.argv.slice(0, 2);
|
|
8983
|
+
var _tail = process.argv.slice(2);
|
|
8984
|
+
var { mode: _mode, color: _color, restArgv: _restArgv } = applyCommonFlags(_tail);
|
|
8985
|
+
setUI(new UI({ mode: _mode, color: _color }));
|
|
8986
|
+
if (_mode === "pretty") {
|
|
8987
|
+
displayBanner();
|
|
8988
|
+
}
|
|
8989
|
+
program.parse([..._head, ..._restArgv]);
|
|
7423
8990
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7424
8991
|
0 && (module.exports = {
|
|
7425
8992
|
getAPIClient,
|