@prismer/sdk 1.7.3 → 1.7.4

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/dist/cli.js CHANGED
@@ -1125,6 +1125,317 @@ var ENVIRONMENTS = {
1125
1125
  production: "https://prismer.cloud"
1126
1126
  };
1127
1127
 
1128
+ // src/aip.ts
1129
+ var import_aip_sdk = require("@prismer/aip-sdk");
1130
+ var import_aip_sdk2 = require("@prismer/aip-sdk");
1131
+ var import_aip_sdk3 = require("@prismer/aip-sdk");
1132
+ var import_aip_sdk4 = require("@prismer/aip-sdk");
1133
+
1134
+ // src/encryption.ts
1135
+ function getSubtleCrypto() {
1136
+ if (typeof globalThis.crypto?.subtle !== "undefined") {
1137
+ return globalThis.crypto.subtle;
1138
+ }
1139
+ try {
1140
+ const { webcrypto } = require("crypto");
1141
+ return webcrypto.subtle;
1142
+ } catch {
1143
+ throw new Error("No SubtleCrypto available. Requires browser or Node.js 16+.");
1144
+ }
1145
+ }
1146
+ function getRandomValues(arr) {
1147
+ if (typeof globalThis.crypto?.getRandomValues !== "undefined") {
1148
+ return globalThis.crypto.getRandomValues(arr);
1149
+ }
1150
+ try {
1151
+ const { webcrypto } = require("crypto");
1152
+ return webcrypto.getRandomValues(arr);
1153
+ } catch {
1154
+ throw new Error("No crypto.getRandomValues available.");
1155
+ }
1156
+ }
1157
+ var subtle = () => getSubtleCrypto();
1158
+ var PBKDF2_ITERATIONS = 1e5;
1159
+ var SALT_LENGTH = 16;
1160
+ var IV_LENGTH = 12;
1161
+ var KEY_LENGTH = 256;
1162
+ var _E2EEncryption = class _E2EEncryption {
1163
+ constructor() {
1164
+ this.masterKey = null;
1165
+ this.keyPair = null;
1166
+ this.sessionKeys = /* @__PURE__ */ new Map();
1167
+ // conversationId → AES key
1168
+ this.salt = null;
1169
+ // ─── Pipeline Functions ──────────────────────────────────
1170
+ this.messageCount = 0;
1171
+ this.lastRotation = Date.now();
1172
+ }
1173
+ /**
1174
+ * Initialize encryption with user passphrase.
1175
+ * Derives a master key via PBKDF2 and generates an ECDH key pair.
1176
+ *
1177
+ * @param passphrase - User passphrase for master key derivation
1178
+ * @param salt - Optional Base64-encoded salt. If omitted, a random 16-byte salt is generated.
1179
+ * Store the salt (via exportSalt()) so you can re-derive the same master key later.
1180
+ */
1181
+ async init(passphrase, salt) {
1182
+ this.salt = salt ? new Uint8Array(base64ToArrayBuffer(salt)) : getRandomValues(new Uint8Array(SALT_LENGTH));
1183
+ const passphraseKey = await subtle().importKey(
1184
+ "raw",
1185
+ new TextEncoder().encode(passphrase),
1186
+ "PBKDF2",
1187
+ false,
1188
+ ["deriveKey"]
1189
+ );
1190
+ this.masterKey = await subtle().deriveKey(
1191
+ {
1192
+ name: "PBKDF2",
1193
+ salt: new Uint8Array(this.salt.buffer, this.salt.byteOffset, this.salt.byteLength).buffer,
1194
+ iterations: PBKDF2_ITERATIONS,
1195
+ hash: "SHA-256"
1196
+ },
1197
+ passphraseKey,
1198
+ { name: "AES-GCM", length: KEY_LENGTH },
1199
+ false,
1200
+ ["encrypt", "decrypt"]
1201
+ );
1202
+ this.keyPair = await subtle().generateKey(
1203
+ { name: "ECDH", namedCurve: "P-256" },
1204
+ true,
1205
+ ["deriveKey"]
1206
+ );
1207
+ }
1208
+ /**
1209
+ * Export the salt as Base64 string for persistent storage.
1210
+ * You must store this and pass it back to init() to re-derive the same master key.
1211
+ */
1212
+ exportSalt() {
1213
+ if (!this.salt) throw new Error("E2E not initialized. Call init() first.");
1214
+ return arrayBufferToBase64(this.salt.buffer);
1215
+ }
1216
+ /**
1217
+ * Export public key for sharing with conversation peers.
1218
+ */
1219
+ async exportPublicKey() {
1220
+ if (!this.keyPair) throw new Error("E2E not initialized. Call init() first.");
1221
+ return subtle().exportKey("jwk", this.keyPair.publicKey);
1222
+ }
1223
+ /**
1224
+ * Derive a shared session key for a conversation using ECDH.
1225
+ * Call this with each peer's public key.
1226
+ */
1227
+ async deriveSessionKey(conversationId, peerPublicKey) {
1228
+ if (!this.keyPair) throw new Error("E2E not initialized. Call init() first.");
1229
+ const importedPeerKey = await subtle().importKey(
1230
+ "jwk",
1231
+ peerPublicKey,
1232
+ { name: "ECDH", namedCurve: "P-256" },
1233
+ false,
1234
+ []
1235
+ );
1236
+ const sessionKey = await subtle().deriveKey(
1237
+ { name: "ECDH", public: importedPeerKey },
1238
+ this.keyPair.privateKey,
1239
+ { name: "AES-GCM", length: KEY_LENGTH },
1240
+ false,
1241
+ ["encrypt", "decrypt"]
1242
+ );
1243
+ this.sessionKeys.set(conversationId, sessionKey);
1244
+ }
1245
+ /**
1246
+ * Set a pre-shared session key for a conversation.
1247
+ * Useful when the key is exchanged out-of-band or derived from a group key.
1248
+ */
1249
+ async setSessionKey(conversationId, rawKey) {
1250
+ const key = await subtle().importKey(
1251
+ "raw",
1252
+ rawKey,
1253
+ { name: "AES-GCM", length: KEY_LENGTH },
1254
+ false,
1255
+ ["encrypt", "decrypt"]
1256
+ );
1257
+ this.sessionKeys.set(conversationId, key);
1258
+ }
1259
+ /**
1260
+ * Generate a random session key for a conversation.
1261
+ * Returns the raw key bytes for sharing with peers.
1262
+ */
1263
+ async generateSessionKey(conversationId) {
1264
+ const key = await subtle().generateKey(
1265
+ { name: "AES-GCM", length: KEY_LENGTH },
1266
+ true,
1267
+ ["encrypt", "decrypt"]
1268
+ );
1269
+ this.sessionKeys.set(conversationId, key);
1270
+ return subtle().exportKey("raw", key);
1271
+ }
1272
+ /**
1273
+ * Encrypt plaintext for a conversation.
1274
+ * Returns base64-encoded ciphertext with prepended IV.
1275
+ */
1276
+ async encrypt(conversationId, plaintext) {
1277
+ const key = this.sessionKeys.get(conversationId);
1278
+ if (!key) throw new Error(`No session key for conversation ${conversationId}. Call deriveSessionKey() first.`);
1279
+ const iv = getRandomValues(new Uint8Array(IV_LENGTH));
1280
+ const encoded = new TextEncoder().encode(plaintext);
1281
+ const ciphertext = await subtle().encrypt(
1282
+ { name: "AES-GCM", iv },
1283
+ key,
1284
+ encoded
1285
+ );
1286
+ const combined = new Uint8Array(iv.length + ciphertext.byteLength);
1287
+ combined.set(iv, 0);
1288
+ combined.set(new Uint8Array(ciphertext), iv.length);
1289
+ return arrayBufferToBase64(combined.buffer);
1290
+ }
1291
+ /**
1292
+ * Decrypt ciphertext from a conversation.
1293
+ * Expects base64-encoded data with prepended IV.
1294
+ */
1295
+ async decrypt(conversationId, ciphertext) {
1296
+ const key = this.sessionKeys.get(conversationId);
1297
+ if (!key) throw new Error(`No session key for conversation ${conversationId}. Call deriveSessionKey() first.`);
1298
+ const combined = base64ToArrayBuffer(ciphertext);
1299
+ const iv = combined.slice(0, IV_LENGTH);
1300
+ const data = combined.slice(IV_LENGTH);
1301
+ const decrypted = await subtle().decrypt(
1302
+ { name: "AES-GCM", iv: new Uint8Array(iv) },
1303
+ key,
1304
+ data
1305
+ );
1306
+ return new TextDecoder().decode(decrypted);
1307
+ }
1308
+ /**
1309
+ * Check if a session key exists for a conversation.
1310
+ */
1311
+ hasSessionKey(conversationId) {
1312
+ return this.sessionKeys.has(conversationId);
1313
+ }
1314
+ /**
1315
+ * Remove session key for a conversation.
1316
+ */
1317
+ removeSessionKey(conversationId) {
1318
+ this.sessionKeys.delete(conversationId);
1319
+ }
1320
+ /**
1321
+ * Clear all keys and reset state.
1322
+ */
1323
+ destroy() {
1324
+ this.masterKey = null;
1325
+ this.keyPair = null;
1326
+ this.sessionKeys.clear();
1327
+ this.salt = null;
1328
+ this.messageCount = 0;
1329
+ }
1330
+ /**
1331
+ * High-level encrypt-for-send pipeline.
1332
+ * Encrypts content, builds metadata, and handles key rotation.
1333
+ *
1334
+ * Returns { encryptedContent, metadata } ready to send.
1335
+ */
1336
+ async encryptForSend(conversationId, content) {
1337
+ if (!this.hasSessionKey(conversationId)) {
1338
+ throw new Error("No session key for this conversation. Call deriveSessionKey() first.");
1339
+ }
1340
+ const needsRotation = this.shouldRotateKey();
1341
+ const encryptedContent = await this.encrypt(conversationId, content);
1342
+ this.messageCount++;
1343
+ return {
1344
+ encryptedContent,
1345
+ metadata: {
1346
+ encrypted: true,
1347
+ encryptionVersion: 1,
1348
+ ...needsRotation && { keyRotationRequested: true }
1349
+ }
1350
+ };
1351
+ }
1352
+ /**
1353
+ * High-level decrypt-on-receive pipeline.
1354
+ * Decrypts content and validates metadata.
1355
+ */
1356
+ async decryptOnReceive(conversationId, encryptedContent, metadata) {
1357
+ if (!this.hasSessionKey(conversationId)) {
1358
+ throw new Error("No session key for this conversation. Call deriveSessionKey() first.");
1359
+ }
1360
+ return this.decrypt(conversationId, encryptedContent);
1361
+ }
1362
+ /**
1363
+ * High-level file encryption pipeline.
1364
+ */
1365
+ async encryptFile(conversationId, fileData) {
1366
+ const base64Data = arrayBufferToBase64(fileData);
1367
+ const encryptedData = await this.encrypt(conversationId, base64Data);
1368
+ return {
1369
+ encryptedData,
1370
+ metadata: {
1371
+ encrypted: true,
1372
+ encryptionVersion: 1,
1373
+ fileEncrypted: true
1374
+ }
1375
+ };
1376
+ }
1377
+ /**
1378
+ * High-level file decryption pipeline.
1379
+ */
1380
+ async decryptFile(conversationId, encryptedData) {
1381
+ const base64Data = await this.decrypt(conversationId, encryptedData);
1382
+ return base64ToArrayBuffer(base64Data);
1383
+ }
1384
+ /**
1385
+ * Check if key rotation is needed (1000 messages or 24 hours).
1386
+ */
1387
+ shouldRotateKey() {
1388
+ if (this.messageCount >= _E2EEncryption.KEY_ROTATION_THRESHOLD) {
1389
+ return true;
1390
+ }
1391
+ if (Date.now() - this.lastRotation >= _E2EEncryption.KEY_ROTATION_INTERVAL_MS) {
1392
+ return true;
1393
+ }
1394
+ return false;
1395
+ }
1396
+ /**
1397
+ * Perform key rotation: generate new ECDH keypair and reset counters.
1398
+ * The caller is responsible for re-exchanging keys with peers.
1399
+ */
1400
+ async rotateKeys() {
1401
+ this.keyPair = await subtle().generateKey(
1402
+ { name: "ECDH", namedCurve: "P-256" },
1403
+ false,
1404
+ ["deriveKey"]
1405
+ );
1406
+ this.messageCount = 0;
1407
+ this.lastRotation = Date.now();
1408
+ this.sessionKeys.clear();
1409
+ return this.exportPublicKey();
1410
+ }
1411
+ };
1412
+ _E2EEncryption.KEY_ROTATION_THRESHOLD = 1e3;
1413
+ _E2EEncryption.KEY_ROTATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1414
+ var E2EEncryption = _E2EEncryption;
1415
+ function arrayBufferToBase64(buffer) {
1416
+ if (typeof btoa !== "undefined") {
1417
+ const bytes = new Uint8Array(buffer);
1418
+ let binary = "";
1419
+ for (let i = 0; i < bytes.byteLength; i++) {
1420
+ binary += String.fromCharCode(bytes[i]);
1421
+ }
1422
+ return btoa(binary);
1423
+ }
1424
+ return Buffer.from(buffer).toString("base64");
1425
+ }
1426
+ function base64ToArrayBuffer(base64) {
1427
+ if (typeof atob !== "undefined") {
1428
+ const binary = atob(base64);
1429
+ const bytes = new Uint8Array(binary.length);
1430
+ for (let i = 0; i < binary.length; i++) {
1431
+ bytes[i] = binary.charCodeAt(i);
1432
+ }
1433
+ return bytes.buffer;
1434
+ }
1435
+ const buf = Buffer.from(base64, "base64");
1436
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1437
+ }
1438
+
1128
1439
  // src/index.ts
1129
1440
  var AccountClient = class {
1130
1441
  constructor(_r) {
@@ -1607,11 +1918,11 @@ var EvolutionClient = class {
1607
1918
  }
1608
1919
  /** Delete a gene */
1609
1920
  async deleteGene(geneId) {
1610
- return this._r("DELETE", `/api/im/evolution/genes/${geneId}`);
1921
+ return this._r("DELETE", `/api/im/evolution/genes/${encodeURIComponent(geneId)}`);
1611
1922
  }
1612
1923
  /** Publish a gene. Pass skipCanary=true to bypass canary validation (MVP/admin). */
1613
1924
  async publishGene(geneId, options) {
1614
- return this._r("POST", `/api/im/evolution/genes/${geneId}/publish`, options?.skipCanary ? { skipCanary: true } : void 0);
1925
+ return this._r("POST", `/api/im/evolution/genes/${encodeURIComponent(geneId)}/publish`, options?.skipCanary ? { skipCanary: true } : void 0);
1615
1926
  }
1616
1927
  /** Import a published gene */
1617
1928
  async importGene(geneId) {
@@ -1697,6 +2008,14 @@ var EvolutionClient = class {
1697
2008
  async getSkillContent(slugOrId) {
1698
2009
  return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}/content`);
1699
2010
  }
2011
+ /** Create/submit a community skill */
2012
+ async createSkill(input) {
2013
+ return this._r("POST", "/api/im/skills", input);
2014
+ }
2015
+ /** Star a skill (increment community rating) */
2016
+ async starSkill(skillId) {
2017
+ return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
2018
+ }
1700
2019
  /**
1701
2020
  * Install a skill and write SKILL.md to local filesystem.
1702
2021
  * Combines cloud install + local file sync for Claude Code / OpenClaw / OpenCode.
@@ -1759,8 +2078,8 @@ var EvolutionClient = class {
1759
2078
  async uninstallSkillLocal(slugOrId) {
1760
2079
  const result = await this.uninstallSkill(slugOrId);
1761
2080
  const removedPaths = [];
1762
- const safeSlug = slugOrId.replace(/[\/\\]/g, "").replace(/\.\./g, "");
1763
- if (!safeSlug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
2081
+ const slug = safeSlug(slugOrId);
2082
+ if (!slug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
1764
2083
  try {
1765
2084
  const fs2 = await import("fs");
1766
2085
  const path2 = await import("path");
@@ -1768,10 +2087,10 @@ var EvolutionClient = class {
1768
2087
  const home = os2.homedir();
1769
2088
  const pluginBase = process.env.PRISMER_PLUGIN_DIR || path2.join(home, ".claude", "plugins", "prismer");
1770
2089
  const dirs = [
1771
- path2.join(home, ".claude", "skills", safeSlug),
1772
- path2.join(home, ".openclaw", "skills", safeSlug),
1773
- path2.join(home, ".config", "opencode", "skills", safeSlug),
1774
- path2.join(pluginBase, "skills", safeSlug)
2090
+ path2.join(home, ".claude", "skills", slug),
2091
+ path2.join(home, ".openclaw", "skills", slug),
2092
+ path2.join(home, ".config", "opencode", "skills", slug),
2093
+ path2.join(pluginBase, "skills", slug)
1775
2094
  ];
1776
2095
  for (const dir of dirs) {
1777
2096
  try {
@@ -1881,6 +2200,9 @@ var EvolutionClient = class {
1881
2200
  return this._r("POST", "/api/im/evolution/sync", body);
1882
2201
  }
1883
2202
  };
2203
+ function safeSlug(input) {
2204
+ return input.replace(/[\/\\]/g, "").replace(/\.\./g, "").replace(/\0/g, "");
2205
+ }
1884
2206
  function guessMimeType(fileName) {
1885
2207
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
1886
2208
  const map = {
@@ -3285,6 +3607,52 @@ function register3(parent, getIMClient2, _getAPIClient) {
3285
3607
  handleError(err);
3286
3608
  }
3287
3609
  });
3610
+ evolve.command("publish <gene-id>").description("Publish a private gene to the evolution network").option("--skip-canary", "skip canary phase and publish directly").option("--json", "output raw JSON response").action(async (geneId, opts) => {
3611
+ const client = getIMClient2();
3612
+ try {
3613
+ const res = await client.im.evolution.publishGene(geneId, { skipCanary: opts.skipCanary });
3614
+ if (opts.json) {
3615
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
3616
+ return;
3617
+ }
3618
+ printResult(res, `Gene ${geneId} published${opts.skipCanary ? " (skipped canary)" : " (canary phase)"}`);
3619
+ } catch (err) {
3620
+ handleError(err);
3621
+ }
3622
+ });
3623
+ evolve.command("fork <gene-id>").description("Fork a public gene with optional modifications").option("--strategy <steps...>", "override strategy steps").option("--json", "output raw JSON response").action(async (geneId, opts) => {
3624
+ const client = getIMClient2();
3625
+ try {
3626
+ const res = await client.im.evolution.forkGene({
3627
+ gene_id: geneId,
3628
+ modifications: opts.strategy ? { strategy: opts.strategy } : void 0
3629
+ });
3630
+ if (opts.json) {
3631
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
3632
+ return;
3633
+ }
3634
+ printResult(res);
3635
+ const data = res.data;
3636
+ const newId = data?.id ?? data?.gene_id ?? "unknown";
3637
+ process.stdout.write(`Forked gene ${geneId} \u2192 ${newId}
3638
+ `);
3639
+ } catch (err) {
3640
+ handleError(err);
3641
+ }
3642
+ });
3643
+ evolve.command("delete <gene-id>").description("Delete a gene you own").option("--json", "output raw JSON response").action(async (geneId, opts) => {
3644
+ const client = getIMClient2();
3645
+ try {
3646
+ const res = await client.im.evolution.deleteGene(geneId);
3647
+ if (opts.json) {
3648
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
3649
+ return;
3650
+ }
3651
+ printResult(res, `Gene ${geneId} deleted`);
3652
+ } catch (err) {
3653
+ handleError(err);
3654
+ }
3655
+ });
3288
3656
  evolve.command("import <gene-id>").description("Import a published gene into your collection").option("--json", "output raw JSON response").action(async (geneId, opts) => {
3289
3657
  const client = getIMClient2();
3290
3658
  try {
@@ -4619,7 +4987,7 @@ function readConfig() {
4619
4987
  }
4620
4988
  function writeConfig(config) {
4621
4989
  ensureConfigDir();
4622
- fs.writeFileSync(CONFIG_PATH, TOML.stringify(config), "utf-8");
4990
+ fs.writeFileSync(CONFIG_PATH, TOML.stringify(config), { encoding: "utf-8", mode: 384 });
4623
4991
  }
4624
4992
  function setNestedValue(obj, dotPath, value) {
4625
4993
  const parts = dotPath.split(".");
@@ -4635,7 +5003,7 @@ function getIMClient() {
4635
5003
  const cfg = readConfig();
4636
5004
  const token = cfg?.auth?.im_token;
4637
5005
  if (!token) {
4638
- console.error('No IM token. Run "prismer register" first.');
5006
+ console.error('No IM token. Run "prismer setup --agent" or "prismer register <username>" first.');
4639
5007
  process.exit(1);
4640
5008
  }
4641
5009
  const env = cfg?.default?.environment || "production";
@@ -4646,7 +5014,7 @@ function getAPIClient() {
4646
5014
  const cfg = readConfig();
4647
5015
  const apiKey = cfg?.default?.api_key;
4648
5016
  if (!apiKey) {
4649
- console.error('No API key. Run "prismer init <api-key>" first.');
5017
+ console.error('No API key. Run "prismer setup" to sign in and get your key.');
4650
5018
  process.exit(1);
4651
5019
  }
4652
5020
  const env = cfg?.default?.environment || "production";
@@ -4655,20 +5023,191 @@ function getAPIClient() {
4655
5023
  }
4656
5024
  var program = new import_commander.Command();
4657
5025
  program.name("prismer").description("Prismer Cloud SDK CLI").version(cliVersion);
4658
- program.command("init <api-key>").description("Store API key in ~/.prismer/config.toml").action((apiKey) => {
4659
- const config = readConfig();
5026
+ async function verifyAndSaveKey(config, apiKey) {
5027
+ if (!apiKey) {
5028
+ console.error("No key provided.");
5029
+ process.exit(1);
5030
+ }
5031
+ if (!apiKey.startsWith("sk-prismer-")) {
5032
+ console.error("Invalid key format. API keys start with sk-prismer-");
5033
+ console.error("Get your key at: https://prismer.cloud/setup");
5034
+ process.exit(1);
5035
+ }
5036
+ const baseUrl = config.default?.base_url || "https://prismer.cloud";
5037
+ try {
5038
+ const res = await fetch(`${baseUrl}/api/version`, {
5039
+ headers: { Authorization: `Bearer ${apiKey}` }
5040
+ });
5041
+ if (res.status === 401) {
5042
+ console.error("API key is invalid or expired.");
5043
+ console.error("Get a new key at: https://prismer.cloud/setup");
5044
+ process.exit(1);
5045
+ }
5046
+ console.log("API key verified \u2713");
5047
+ } catch (err) {
5048
+ console.warn(`Could not verify key (${err.message}). Saving anyway.`);
5049
+ }
4660
5050
  if (!config.default) config.default = {};
4661
5051
  config.default.api_key = apiKey;
4662
5052
  if (!config.default.environment) config.default.environment = "production";
4663
- if (config.default.base_url === void 0) config.default.base_url = "";
4664
5053
  writeConfig(config);
4665
- console.log("API key saved to ~/.prismer/config.toml");
5054
+ console.log("");
5055
+ console.log("Saved to ~/.prismer/config.toml");
5056
+ console.log("You can now use: CLI commands, MCP tools, Claude Code plugin, and all SDKs.");
5057
+ }
5058
+ function openBrowser(url) {
5059
+ const { execFile } = require("child_process");
5060
+ if (process.platform === "darwin") {
5061
+ execFile("open", [url], (err) => {
5062
+ if (err) console.warn("Could not open browser. Please open the URL above manually.");
5063
+ });
5064
+ } else if (process.platform === "win32") {
5065
+ execFile("cmd.exe", ["/c", "start", "", url], (err) => {
5066
+ if (err) console.warn("Could not open browser. Please open the URL above manually.");
5067
+ });
5068
+ } else {
5069
+ execFile("xdg-open", [url], (err) => {
5070
+ if (err) console.warn("Could not open browser. Please open the URL above manually.");
5071
+ });
5072
+ }
5073
+ }
5074
+ async function runSetup(opts, apiKey) {
5075
+ const config = readConfig();
5076
+ if (!config.default) config.default = {};
5077
+ const baseUrl = config.default.base_url || "https://prismer.cloud";
5078
+ if (!opts.force && config.default.api_key?.startsWith("sk-prismer-")) {
5079
+ const masked = config.default.api_key.slice(0, 12) + "..." + config.default.api_key.slice(-4);
5080
+ console.log(`Already configured: ${masked}`);
5081
+ console.log("");
5082
+ console.log("To reconfigure, run: prismer setup --force");
5083
+ console.log("To check status: prismer status");
5084
+ return;
5085
+ }
5086
+ if (apiKey) {
5087
+ await verifyAndSaveKey(config, apiKey);
5088
+ return;
5089
+ }
5090
+ if (opts.agent) {
5091
+ if (!opts.force && config.auth?.im_token) {
5092
+ console.log("Already registered as agent (IM token exists).");
5093
+ console.log("For API key access, run: prismer setup");
5094
+ return;
5095
+ }
5096
+ const username = `agent-${Date.now().toString(36)}`;
5097
+ try {
5098
+ const res = await fetch(`${baseUrl}/api/im/register`, {
5099
+ method: "POST",
5100
+ headers: { "Content-Type": "application/json" },
5101
+ body: JSON.stringify({ username, displayName: username, type: "agent" })
5102
+ });
5103
+ const data = await res.json();
5104
+ if (!data.ok) throw new Error(data.error?.message || "Registration failed");
5105
+ if (!config.auth) config.auth = {};
5106
+ config.auth.im_token = data.data?.token;
5107
+ config.auth.im_user_id = data.data?.imUserId || data.data?.userId;
5108
+ config.auth.im_username = data.data?.username || username;
5109
+ writeConfig(config);
5110
+ console.log("Agent registered with free credits \u2713");
5111
+ console.log(` Username: ${config.auth.im_username}`);
5112
+ console.log(` User ID: ${config.auth.im_user_id}`);
5113
+ console.log("");
5114
+ console.log("For full API access, sign in: prismer setup");
5115
+ } catch (err) {
5116
+ console.error(`Agent registration failed: ${err.message}`);
5117
+ console.error("Try signing in instead: prismer setup");
5118
+ process.exit(1);
5119
+ }
5120
+ return;
5121
+ }
5122
+ if (opts.manual) {
5123
+ const setupUrl = `${baseUrl}/setup?utm_source=cli&utm_medium=manual`;
5124
+ console.log("Opening browser to sign in...");
5125
+ console.log(` ${setupUrl}`);
5126
+ console.log("");
5127
+ openBrowser(setupUrl);
5128
+ console.log("After signing in, copy the API key from the page and paste it below.");
5129
+ console.log("");
5130
+ const readline = require("readline");
5131
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
5132
+ rl.question("Paste your API key: ", (key) => {
5133
+ rl.close();
5134
+ verifyAndSaveKey(config, key.trim()).catch((err) => {
5135
+ console.error(`Setup failed: ${err.message}`);
5136
+ process.exit(1);
5137
+ });
5138
+ });
5139
+ return;
5140
+ }
5141
+ const http = require("http");
5142
+ const crypto2 = require("crypto");
5143
+ const state = crypto2.randomBytes(16).toString("hex");
5144
+ let resolved = false;
5145
+ const server = http.createServer((req, res) => {
5146
+ const url = new URL(req.url, `http://localhost`);
5147
+ if (url.pathname === "/callback") {
5148
+ const key = url.searchParams.get("key");
5149
+ const returnedState = url.searchParams.get("state");
5150
+ res.writeHead(200, { "Content-Type": "text/html" });
5151
+ if (!key || !returnedState || returnedState !== state) {
5152
+ res.end('<html><head><meta name="referrer" content="no-referrer"></head><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Setup failed</h2><p>Invalid or missing parameters. Please try again.</p></body></html>');
5153
+ return;
5154
+ }
5155
+ if (!key.startsWith("sk-prismer-")) {
5156
+ res.end('<html><head><meta name="referrer" content="no-referrer"></head><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Invalid key</h2><p>The key format is unexpected. Please try again.</p></body></html>');
5157
+ return;
5158
+ }
5159
+ res.end('<html><head><meta name="referrer" content="no-referrer"></head><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Done!</h2><p>API key received. You can close this tab.</p></body></html>');
5160
+ resolved = true;
5161
+ verifyAndSaveKey(config, key).then(() => {
5162
+ server.close();
5163
+ process.exit(0);
5164
+ }).catch((err) => {
5165
+ console.error(`Setup failed: ${err.message}`);
5166
+ server.close();
5167
+ process.exit(1);
5168
+ });
5169
+ } else {
5170
+ res.writeHead(404);
5171
+ res.end("Not found");
5172
+ }
5173
+ });
5174
+ server.listen(0, "127.0.0.1", () => {
5175
+ const port = server.address().port;
5176
+ const callbackUrl = `http://127.0.0.1:${port}/callback`;
5177
+ const setupUrl = `${baseUrl}/setup?callback=${encodeURIComponent(callbackUrl)}&state=${state}&utm_source=cli&utm_medium=auto`;
5178
+ console.log("Opening browser to sign in...");
5179
+ console.log("");
5180
+ openBrowser(setupUrl);
5181
+ console.log("Waiting for authentication...");
5182
+ console.log("(If the browser didn't open, visit this URL manually:)");
5183
+ console.log(` ${setupUrl}`);
5184
+ console.log("");
5185
+ setTimeout(() => {
5186
+ if (!resolved) {
5187
+ console.error("Timed out waiting for authentication (5 min).");
5188
+ console.error("");
5189
+ console.error("Alternatives:");
5190
+ console.error(" prismer setup --manual Paste key manually");
5191
+ console.error(" prismer setup --agent Register as agent (free credits, no browser)");
5192
+ server.close();
5193
+ process.exit(1);
5194
+ }
5195
+ }, 5 * 60 * 1e3);
5196
+ });
5197
+ }
5198
+ 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) => {
5199
+ await runSetup(opts, apiKey);
5200
+ });
5201
+ program.command("init [api-key]").description('Alias for "prismer 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) => {
5202
+ console.log('Note: "prismer init" is deprecated. Use "prismer setup" instead.');
5203
+ console.log("");
5204
+ await runSetup(opts, apiKey);
4666
5205
  });
4667
5206
  program.command("register <username>").description("Register an IM identity and store the token").option("--type <type>", "Identity type: agent or human", "agent").option("--display-name <name>", "Display name").option("--agent-type <agentType>", "Agent type: assistant, specialist, orchestrator, tool, bot").option("--capabilities <caps>", "Comma-separated capabilities").option("--endpoint <url>", "Webhook endpoint URL").option("--webhook-secret <secret>", "Webhook HMAC secret").action(async (username, opts) => {
4668
5207
  const config = readConfig();
4669
5208
  const apiKey = config.default?.api_key;
4670
5209
  if (!apiKey) {
4671
- console.error('No API key. Run "prismer init <api-key>" first.');
5210
+ console.error('No API key. Run "prismer setup" first.');
4672
5211
  process.exit(1);
4673
5212
  }
4674
5213
  const client = new PrismerClient({
@@ -4766,7 +5305,7 @@ program.command("status").description("Show current config and live info").actio
4766
5305
  var configCmd = program.command("config").description("Manage config file");
4767
5306
  configCmd.command("show").description("Print config file").action(() => {
4768
5307
  if (!fs.existsSync(CONFIG_PATH)) {
4769
- console.log('No config file. Run "prismer init <api-key>" to create one.');
5308
+ console.log('No config file. Run "prismer setup" to create one.');
4770
5309
  return;
4771
5310
  }
4772
5311
  console.log(fs.readFileSync(CONFIG_PATH, "utf-8"));