@piut/cli 3.8.0 → 3.9.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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +207 -59
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -169,7 +169,7 @@ Three ways to connect your brain to your AI tools:
169
169
 
170
170
  For scripting: `piut setup --key pb_YOUR_KEY --yes`
171
171
 
172
- **Supported tools:** Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed, ChatGPT, OpenClaw, Msty, OpenAI Agents SDK, Claude Agent SDK, and any MCP client.
172
+ **Supported tools:** Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Amazon Q, Zed, ChatGPT, OpenClaw, Msty, OpenAI Agents SDK, Claude Agent SDK, and any MCP client.
173
173
 
174
174
  ### skill.md Reference
175
175
 
@@ -180,7 +180,7 @@ Add a reference to [skill.md](skill.md) in your tool's rules file so your AI kno
180
180
  | Claude Code | CLAUDE.md or ~/.claude/CLAUDE.md |
181
181
  | Cursor | .cursor/rules/piut.mdc |
182
182
  | Windsurf | .windsurf/rules/piut.md |
183
- | GitHub Copilot | .github/copilot-instructions.md |
183
+ | VS Code | .github/copilot-instructions.md |
184
184
  | Claude Desktop | Project Knowledge (upload file) |
185
185
  | ChatGPT | Settings > Custom Instructions |
186
186
  | Zed | .zed/rules.md |
@@ -224,7 +224,7 @@ piut sync --pull # Pull cloud changes
224
224
  | `AGENTS.md` | Multi-agent | Repo root |
225
225
  | `.cursorrules` | Cursor | Repo root |
226
226
  | `.windsurfrules` | Windsurf | Repo root |
227
- | `copilot-instructions.md` | GitHub Copilot | .github/ |
227
+ | `copilot-instructions.md` | VS Code | .github/ |
228
228
  | `MEMORY.md` | Claude Code | ~/.claude/ |
229
229
  | `SOUL.md` | OpenClaw | ~/.openclaw/workspace/ |
230
230
  | `rules/*.md` | Various | .cursor/rules/, .claude/rules/ |
package/dist/cli.js CHANGED
@@ -28,7 +28,7 @@ __export(tree_prompt_exports, {
28
28
  loadChildren: () => loadChildren,
29
29
  shouldShowInTree: () => shouldShowInTree
30
30
  });
31
- import fs13 from "fs";
31
+ import fs14 from "fs";
32
32
  import path14 from "path";
33
33
  import os8 from "os";
34
34
  import {
@@ -55,7 +55,7 @@ function shouldShowFile(name) {
55
55
  }
56
56
  function loadChildren(parentPath, parentDepth, includeFiles = false) {
57
57
  try {
58
- const entries = fs13.readdirSync(parentPath, { withFileTypes: true });
58
+ const entries = fs14.readdirSync(parentPath, { withFileTypes: true });
59
59
  const dirs = [];
60
60
  const files = [];
61
61
  for (const entry of entries) {
@@ -384,29 +384,33 @@ async function* buildBrainStreaming(key, input2) {
384
384
  const reader = res.body.getReader();
385
385
  const decoder = new TextDecoder();
386
386
  let buffer = "";
387
- while (true) {
388
- const { done, value } = await reader.read();
389
- if (done) break;
390
- buffer += decoder.decode(value, { stream: true });
391
- const parts = buffer.split("\n\n");
392
- buffer = parts.pop() || "";
393
- for (const part of parts) {
394
- let eventName = "";
395
- let eventData = "";
396
- for (const line of part.split("\n")) {
397
- if (line.startsWith("event: ")) {
398
- eventName = line.slice(7).trim();
399
- } else if (line.startsWith("data: ")) {
400
- eventData = line.slice(6);
387
+ try {
388
+ while (true) {
389
+ const { done, value } = await reader.read();
390
+ if (done) break;
391
+ buffer += decoder.decode(value, { stream: true });
392
+ const parts = buffer.split("\n\n");
393
+ buffer = parts.pop() || "";
394
+ for (const part of parts) {
395
+ let eventName = "";
396
+ let eventData = "";
397
+ for (const line of part.split("\n")) {
398
+ if (line.startsWith("event: ")) {
399
+ eventName = line.slice(7).trim();
400
+ } else if (line.startsWith("data: ")) {
401
+ eventData = line.slice(6);
402
+ }
401
403
  }
402
- }
403
- if (eventName && eventData) {
404
- try {
405
- yield { event: eventName, data: JSON.parse(eventData) };
406
- } catch {
404
+ if (eventName && eventData) {
405
+ try {
406
+ yield { event: eventName, data: JSON.parse(eventData) };
407
+ } catch {
408
+ }
407
409
  }
408
410
  }
409
411
  }
412
+ } catch {
413
+ yield { event: "error", data: { message: "Connection lost. The build may still complete \u2014 run `piut status` to check." } };
410
414
  }
411
415
  }
412
416
  async function verifyMcpEndpoint(serverUrl, key) {
@@ -700,21 +704,20 @@ var TOOLS = [
700
704
  })
701
705
  },
702
706
  {
703
- id: "copilot",
704
- name: "GitHub Copilot",
707
+ id: "vscode",
708
+ name: "VS Code",
705
709
  configKey: "servers",
706
- globalConfigKey: "mcp.servers",
707
710
  configPaths: {
708
- darwin: ["~/Library/Application Support/Code/User/settings.json"],
709
- win32: [path2.join(appData(), "Code", "User", "settings.json")],
710
- linux: ["~/.config/Code/User/settings.json"],
711
+ darwin: ["~/Library/Application Support/Code/User/mcp.json"],
712
+ win32: [path2.join(appData(), "Code", "User", "mcp.json")],
713
+ linux: ["~/.config/Code/User/mcp.json"],
711
714
  project: [".vscode/mcp.json"]
712
715
  },
713
716
  skillFilePath: ".github/copilot-instructions.md",
714
717
  generateConfig: (slug, key) => ({
715
718
  type: "http",
716
719
  url: MCP_URL(slug),
717
- headers: { ...AUTH_HEADER(key), ...machineHeaders("GitHub Copilot") }
720
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("VS Code") }
718
721
  })
719
722
  },
720
723
  {
@@ -1058,6 +1061,16 @@ function writePiutConfig(projectPath, config) {
1058
1061
  "utf-8"
1059
1062
  );
1060
1063
  }
1064
+ function readPiutConfig(projectPath) {
1065
+ try {
1066
+ const raw = fs3.readFileSync(path6.join(piutDir(projectPath), CONFIG_FILE), "utf-8");
1067
+ const parsed = JSON.parse(raw);
1068
+ if (parsed.slug && parsed.apiKey && parsed.serverUrl) return parsed;
1069
+ return null;
1070
+ } catch {
1071
+ return null;
1072
+ }
1073
+ }
1061
1074
  async function writePiutSkill(projectPath, slug, apiKey) {
1062
1075
  const dir = piutDir(projectPath);
1063
1076
  fs3.mkdirSync(dir, { recursive: true });
@@ -2013,7 +2026,10 @@ import os7 from "os";
2013
2026
  init_esm_shims();
2014
2027
  import { select, input, password as password2 } from "@inquirer/prompts";
2015
2028
  import { exec } from "child_process";
2029
+ import http from "http";
2030
+ import crypto3 from "crypto";
2016
2031
  import chalk4 from "chalk";
2032
+ var API_BASE4 = process.env.PIUT_API_BASE || "https://piut.com";
2017
2033
  function openBrowser(url) {
2018
2034
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2019
2035
  exec(`${cmd} "${url}"`);
@@ -2052,12 +2068,101 @@ async function pasteKeyFlow() {
2052
2068
  return { apiKey, validation };
2053
2069
  }
2054
2070
  async function browserFlow() {
2055
- const url = "https://piut.com/dashboard/keys";
2056
- console.log(dim(` Opening ${brand(url)}...`));
2057
- openBrowser(url);
2058
- console.log(dim(" Copy your API key from the dashboard, then paste it here."));
2059
- console.log();
2060
- return pasteKeyFlow();
2071
+ const state = crypto3.randomBytes(16).toString("hex");
2072
+ return new Promise((resolve, reject) => {
2073
+ let settled = false;
2074
+ const server = http.createServer((req, res) => {
2075
+ const url = new URL(req.url, `http://localhost`);
2076
+ if (url.pathname !== "/callback") {
2077
+ res.writeHead(404);
2078
+ res.end();
2079
+ return;
2080
+ }
2081
+ const key = url.searchParams.get("key");
2082
+ const returnedState = url.searchParams.get("state");
2083
+ if (returnedState !== state) {
2084
+ res.writeHead(400, { "Content-Type": "text/html" });
2085
+ res.end(errorPage("State mismatch. Please try again from the CLI."));
2086
+ cleanup();
2087
+ if (!settled) {
2088
+ settled = true;
2089
+ reject(new CliError("Browser auth state mismatch"));
2090
+ }
2091
+ return;
2092
+ }
2093
+ if (!key || !key.startsWith("pb_")) {
2094
+ res.writeHead(400, { "Content-Type": "text/html" });
2095
+ res.end(errorPage("No valid API key received."));
2096
+ cleanup();
2097
+ if (!settled) {
2098
+ settled = true;
2099
+ reject(new CliError("No API key received from browser"));
2100
+ }
2101
+ return;
2102
+ }
2103
+ res.writeHead(200, { "Content-Type": "text/html" });
2104
+ res.end(successPage());
2105
+ cleanup();
2106
+ validateKey(key).then((validation) => {
2107
+ if (!settled) {
2108
+ settled = true;
2109
+ resolve({ apiKey: key, validation });
2110
+ }
2111
+ }).catch((err) => {
2112
+ if (!settled) {
2113
+ settled = true;
2114
+ reject(err);
2115
+ }
2116
+ });
2117
+ });
2118
+ const timer = setTimeout(() => {
2119
+ cleanup();
2120
+ if (!settled) {
2121
+ settled = true;
2122
+ reject(new CliError("Browser login timed out after 2 minutes. Please try again."));
2123
+ }
2124
+ }, 12e4);
2125
+ function cleanup() {
2126
+ clearTimeout(timer);
2127
+ server.close();
2128
+ }
2129
+ server.listen(0, "127.0.0.1", () => {
2130
+ const { port } = server.address();
2131
+ const authUrl = `${API_BASE4}/cli/auth?port=${port}&state=${state}`;
2132
+ console.log(dim(` Opening ${brand("piut.com")} in your browser...`));
2133
+ openBrowser(authUrl);
2134
+ console.log(dim(" Waiting for browser authorization..."));
2135
+ });
2136
+ server.on("error", (err) => {
2137
+ cleanup();
2138
+ if (!settled) {
2139
+ settled = true;
2140
+ reject(new CliError(`Failed to start callback server: ${err.message}`));
2141
+ }
2142
+ });
2143
+ });
2144
+ }
2145
+ function successPage() {
2146
+ return `<!DOCTYPE html>
2147
+ <html><head><title>p\u0131ut CLI</title></head>
2148
+ <body style="background:#0a0a0a;color:white;font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0">
2149
+ <div style="text-align:center">
2150
+ <div style="font-size:48px;color:#4ade80;margin-bottom:16px">&#10003;</div>
2151
+ <h2 style="margin:0 0 8px">CLI Authorized</h2>
2152
+ <p style="color:#a3a3a3;margin:0">You can close this tab and return to your terminal.</p>
2153
+ </div>
2154
+ </body></html>`;
2155
+ }
2156
+ function errorPage(message) {
2157
+ return `<!DOCTYPE html>
2158
+ <html><head><title>p\u0131ut CLI</title></head>
2159
+ <body style="background:#0a0a0a;color:white;font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0">
2160
+ <div style="text-align:center">
2161
+ <div style="font-size:48px;color:#f87171;margin-bottom:16px">&#10007;</div>
2162
+ <h2 style="margin:0 0 8px">Authorization Failed</h2>
2163
+ <p style="color:#a3a3a3;margin:0">${message}</p>
2164
+ </div>
2165
+ </body></html>`;
2061
2166
  }
2062
2167
  async function promptLogin() {
2063
2168
  const method = await select({
@@ -2071,7 +2176,7 @@ async function promptLogin() {
2071
2176
  {
2072
2177
  name: "Log in with browser",
2073
2178
  value: "browser",
2074
- description: "Open piut.com to get your API key"
2179
+ description: "Sign in via piut.com (opens browser)"
2075
2180
  },
2076
2181
  {
2077
2182
  name: "Paste API key",
@@ -2365,7 +2470,7 @@ var RULE_FILES = [
2365
2470
  detect: (p) => p.hasWindsurfRules || fs9.existsSync(path11.join(p.path, ".windsurf"))
2366
2471
  },
2367
2472
  {
2368
- tool: "GitHub Copilot",
2473
+ tool: "VS Code",
2369
2474
  filePath: ".github/copilot-instructions.md",
2370
2475
  strategy: "append",
2371
2476
  detect: (p) => p.hasCopilotInstructions || fs9.existsSync(path11.join(p.path, ".github"))
@@ -2546,7 +2651,7 @@ async function connectCommand(options) {
2546
2651
  }
2547
2652
  console.log();
2548
2653
  let connected = 0;
2549
- const copilotTool = TOOLS.find((t) => t.id === "copilot");
2654
+ const vscodeTool = TOOLS.find((t) => t.id === "vscode");
2550
2655
  for (const projectPath of selectedPaths) {
2551
2656
  const projectActions = byProject.get(projectPath) || [];
2552
2657
  const projectName = path11.basename(projectPath);
@@ -2554,13 +2659,13 @@ async function connectCommand(options) {
2554
2659
  await writePiutSkill(projectPath, slug, apiKey);
2555
2660
  ensureGitignored(projectPath);
2556
2661
  console.log(success(` \u2713 ${projectName}/.piut/`) + dim(" \u2014 credentials + skill"));
2557
- if (copilotTool) {
2558
- const hasCopilot = fs9.existsSync(path11.join(projectPath, ".github", "copilot-instructions.md")) || fs9.existsSync(path11.join(projectPath, ".github"));
2559
- if (hasCopilot) {
2662
+ if (vscodeTool) {
2663
+ const hasVscode = fs9.existsSync(path11.join(projectPath, ".github", "copilot-instructions.md")) || fs9.existsSync(path11.join(projectPath, ".github"));
2664
+ if (hasVscode) {
2560
2665
  const vscodeMcpPath = path11.join(projectPath, ".vscode", "mcp.json");
2561
- const serverConfig = copilotTool.generateConfig(slug, apiKey);
2562
- mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
2563
- console.log(success(` \u2713 ${projectName}/.vscode/mcp.json`) + dim(" \u2014 Copilot MCP"));
2666
+ const serverConfig = vscodeTool.generateConfig(slug, apiKey);
2667
+ mergeConfig(vscodeMcpPath, vscodeTool.configKey, serverConfig);
2668
+ console.log(success(` \u2713 ${projectName}/.vscode/mcp.json`) + dim(" \u2014 VS Code MCP"));
2564
2669
  }
2565
2670
  }
2566
2671
  for (const action of projectActions) {
@@ -3228,10 +3333,45 @@ async function vaultDeleteCommand(filename, options) {
3228
3333
  // src/commands/interactive.ts
3229
3334
  init_esm_shims();
3230
3335
  import { select as select2, confirm as confirm8, checkbox as checkbox5, Separator } from "@inquirer/prompts";
3231
- import fs14 from "fs";
3336
+ import fs15 from "fs";
3232
3337
  import path15 from "path";
3233
3338
  import { exec as exec2 } from "child_process";
3234
3339
  import chalk13 from "chalk";
3340
+
3341
+ // src/lib/sync.ts
3342
+ init_esm_shims();
3343
+ import fs13 from "fs";
3344
+ function syncStaleConfigs(slug, apiKey, serverUrl) {
3345
+ const updated = [];
3346
+ for (const tool of TOOLS) {
3347
+ if (tool.skillOnly || !tool.generateConfig || !tool.configKey) continue;
3348
+ const paths = resolveConfigPaths(tool);
3349
+ for (const { filePath, configKey } of paths) {
3350
+ if (!fs13.existsSync(filePath)) continue;
3351
+ const piutConfig = getPiutConfig(filePath, configKey);
3352
+ if (!piutConfig) continue;
3353
+ const existingKey = extractKeyFromConfig(piutConfig);
3354
+ const existingSlug = extractSlugFromConfig(piutConfig);
3355
+ const keyStale = !!existingKey && existingKey !== apiKey;
3356
+ const slugStale = !!existingSlug && existingSlug !== slug;
3357
+ if (keyStale || slugStale) {
3358
+ const newConfig = tool.generateConfig(slug, apiKey);
3359
+ mergeConfig(filePath, configKey, newConfig);
3360
+ updated.push(tool.name);
3361
+ }
3362
+ break;
3363
+ }
3364
+ }
3365
+ const cwd = process.cwd();
3366
+ const existing = readPiutConfig(cwd);
3367
+ if (existing && (existing.apiKey !== apiKey || existing.slug !== slug)) {
3368
+ writePiutConfig(cwd, { slug, apiKey, serverUrl });
3369
+ updated.push(".piut/config.json");
3370
+ }
3371
+ return updated;
3372
+ }
3373
+
3374
+ // src/commands/interactive.ts
3235
3375
  var DOCUMENT_EXTENSIONS2 = /* @__PURE__ */ new Set([
3236
3376
  "pdf",
3237
3377
  "docx",
@@ -3278,6 +3418,14 @@ async function interactiveMenu() {
3278
3418
  const auth = await authenticate();
3279
3419
  apiKey = auth.apiKey;
3280
3420
  currentValidation = auth.validation;
3421
+ const synced = syncStaleConfigs(
3422
+ currentValidation.slug,
3423
+ apiKey,
3424
+ currentValidation.serverUrl
3425
+ );
3426
+ if (synced.length > 0) {
3427
+ console.log(dim(` Updated ${synced.length} stale config(s): ${synced.join(", ")}`));
3428
+ }
3281
3429
  console.log();
3282
3430
  if (currentValidation.status === "no_brain") {
3283
3431
  console.log(warning(" You haven\u2019t built a brain yet."));
@@ -3472,8 +3620,8 @@ async function handleConnectTools(apiKey, validation) {
3472
3620
  for (const tool of TOOLS) {
3473
3621
  const paths = resolveConfigPaths(tool);
3474
3622
  for (const { filePath, configKey } of paths) {
3475
- const exists = fs14.existsSync(filePath);
3476
- const parentExists = fs14.existsSync(path15.dirname(filePath));
3623
+ const exists = fs15.existsSync(filePath);
3624
+ const parentExists = fs15.existsSync(path15.dirname(filePath));
3477
3625
  if (exists || parentExists) {
3478
3626
  const connected = exists && !!configKey && isPiutConfigured(filePath, configKey);
3479
3627
  detected.push({ tool, configPath: filePath, resolvedConfigKey: configKey, connected });
@@ -3601,7 +3749,7 @@ async function handleManageProjects(apiKey, validation) {
3601
3749
  await writePiutSkill(project.path, slug, apiKey);
3602
3750
  ensureGitignored(project.path);
3603
3751
  if (copilotTool) {
3604
- const hasCopilot = fs14.existsSync(path15.join(project.path, ".github", "copilot-instructions.md")) || fs14.existsSync(path15.join(project.path, ".github"));
3752
+ const hasCopilot = fs15.existsSync(path15.join(project.path, ".github", "copilot-instructions.md")) || fs15.existsSync(path15.join(project.path, ".github"));
3605
3753
  if (hasCopilot) {
3606
3754
  const vscodeMcpPath = path15.join(project.path, ".vscode", "mcp.json");
3607
3755
  const serverConfig = copilotTool.generateConfig(slug, apiKey);
@@ -3611,14 +3759,14 @@ async function handleManageProjects(apiKey, validation) {
3611
3759
  for (const rule of RULE_FILES) {
3612
3760
  if (!rule.detect(project)) continue;
3613
3761
  const absPath = path15.join(project.path, rule.filePath);
3614
- if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) continue;
3615
- if (rule.strategy === "create" || !fs14.existsSync(absPath)) {
3762
+ if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) continue;
3763
+ if (rule.strategy === "create" || !fs15.existsSync(absPath)) {
3616
3764
  const isAppendType = rule.strategy === "append";
3617
3765
  const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
3618
- fs14.mkdirSync(path15.dirname(absPath), { recursive: true });
3619
- fs14.writeFileSync(absPath, content, "utf-8");
3766
+ fs15.mkdirSync(path15.dirname(absPath), { recursive: true });
3767
+ fs15.writeFileSync(absPath, content, "utf-8");
3620
3768
  } else {
3621
- fs14.appendFileSync(absPath, APPEND_SECTION);
3769
+ fs15.appendFileSync(absPath, APPEND_SECTION);
3622
3770
  }
3623
3771
  }
3624
3772
  toolLine(projectName, success("connected"), "\u2714");
@@ -3637,21 +3785,21 @@ async function handleManageProjects(apiKey, validation) {
3637
3785
  const projectName = path15.basename(project.path);
3638
3786
  for (const dedicatedFile of DEDICATED_FILES) {
3639
3787
  const absPath = path15.join(project.path, dedicatedFile);
3640
- if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) {
3788
+ if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) {
3641
3789
  try {
3642
- fs14.unlinkSync(absPath);
3790
+ fs15.unlinkSync(absPath);
3643
3791
  } catch {
3644
3792
  }
3645
3793
  }
3646
3794
  }
3647
3795
  for (const appendFile of APPEND_FILES) {
3648
3796
  const absPath = path15.join(project.path, appendFile);
3649
- if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) {
3797
+ if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) {
3650
3798
  removePiutSection(absPath);
3651
3799
  }
3652
3800
  }
3653
3801
  const vscodeMcpPath = path15.join(project.path, ".vscode", "mcp.json");
3654
- if (fs14.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
3802
+ if (fs15.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
3655
3803
  removeFromConfig(vscodeMcpPath, "servers");
3656
3804
  }
3657
3805
  removePiutDir(project.path);
@@ -3749,10 +3897,10 @@ async function handleVaultUpload(apiKey) {
3749
3897
  let content;
3750
3898
  let encoding;
3751
3899
  if (isDocument) {
3752
- content = fs14.readFileSync(filePath).toString("base64");
3900
+ content = fs15.readFileSync(filePath).toString("base64");
3753
3901
  encoding = "base64";
3754
3902
  } else {
3755
- content = fs14.readFileSync(filePath, "utf-8");
3903
+ content = fs15.readFileSync(filePath, "utf-8");
3756
3904
  }
3757
3905
  const spinner = new Spinner();
3758
3906
  spinner.start(`Uploading ${filename}...`);
@@ -3828,7 +3976,7 @@ async function handleViewBrain(apiKey) {
3828
3976
  }
3829
3977
 
3830
3978
  // src/cli.ts
3831
- var VERSION = "3.8.0";
3979
+ var VERSION = "3.9.0";
3832
3980
  function withExit(fn) {
3833
3981
  return async (...args2) => {
3834
3982
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piut/cli",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.",
5
5
  "type": "module",
6
6
  "bin": {