@instafy/cli 0.1.8-staging.357 → 0.1.8-staging.365

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 CHANGED
@@ -9,6 +9,7 @@ Run Instafy projects locally and connect them back to Instafy Studio — from an
9
9
  0. Log in once: `instafy login`
10
10
  - Opens a Studio URL; the CLI continues automatically after you sign in.
11
11
  - Also enables Git auth (credential helper) for Instafy Git Service. Disable with `instafy login --no-git-setup`.
12
+ - Multiple accounts: `instafy login --profile work` (then bind folders with `instafy project:init --profile work` or `instafy project:profile work`).
12
13
  - Optional: set defaults with `instafy config set controller-url <url>` / `instafy config set studio-url <url>`
13
14
  1. Link a folder to a project:
14
15
  - VS Code: install the Instafy extension and run `Instafy: Link Workspace to Project`, or
package/dist/auth.js CHANGED
@@ -4,7 +4,7 @@ import * as http from "node:http";
4
4
  import { createRequire } from "node:module";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { stdin as input, stdout as output } from "node:process";
7
- import { clearInstafyCliConfig, getInstafyConfigPath, resolveConfiguredControllerUrl, resolveConfiguredStudioUrl, resolveUserAccessToken, writeInstafyCliConfig, } from "./config.js";
7
+ import { clearInstafyCliConfig, clearInstafyProfileConfig, getInstafyConfigPath, getInstafyProfileConfigPath, resolveConfiguredControllerUrl, resolveConfiguredStudioUrl, resolveUserAccessToken, writeInstafyCliConfig, writeInstafyProfileConfig, } from "./config.js";
8
8
  import { installGitCredentialHelper, uninstallGitCredentialHelper } from "./git-setup.js";
9
9
  const require = createRequire(import.meta.url);
10
10
  const cliVersion = (() => {
@@ -45,11 +45,33 @@ function looksLikeLocalControllerUrl(controllerUrl) {
45
45
  return controllerUrl.includes("127.0.0.1") || controllerUrl.includes("localhost");
46
46
  }
47
47
  }
48
- function deriveDefaultStudioUrl(controllerUrl) {
49
- if (looksLikeLocalControllerUrl(controllerUrl)) {
50
- return "http://localhost:5173";
48
+ async function isStudioHealthy(studioUrl, timeoutMs) {
49
+ const target = new URL("/cli/login", studioUrl).toString();
50
+ const abort = new AbortController();
51
+ const timeout = setTimeout(() => abort.abort(), timeoutMs);
52
+ timeout.unref?.();
53
+ try {
54
+ const response = await fetch(target, { signal: abort.signal });
55
+ return response.ok;
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ finally {
61
+ clearTimeout(timeout);
62
+ }
63
+ }
64
+ async function resolveDefaultStudioUrl(controllerUrl) {
65
+ const hosted = "https://staging.instafy.dev";
66
+ if (isStagingCli) {
67
+ return hosted;
51
68
  }
52
- return "https://staging.instafy.dev";
69
+ if (!looksLikeLocalControllerUrl(controllerUrl)) {
70
+ return hosted;
71
+ }
72
+ const local = "http://localhost:5173";
73
+ const healthy = await isStudioHealthy(local, 250);
74
+ return healthy ? local : hosted;
53
75
  }
54
76
  async function isControllerHealthy(controllerUrl, timeoutMs) {
55
77
  const target = `${controllerUrl.replace(/\/$/, "")}/healthz`;
@@ -212,10 +234,11 @@ async function startCliLoginCallbackServer() {
212
234
  };
213
235
  }
214
236
  export async function login(options) {
237
+ const profile = typeof options.profile === "string" && options.profile.trim() ? options.profile.trim() : null;
215
238
  const explicitControllerUrl = normalizeUrl(options.controllerUrl ?? null) ??
216
239
  normalizeUrl(process.env["INSTAFY_SERVER_URL"] ?? null) ??
217
240
  normalizeUrl(process.env["CONTROLLER_BASE_URL"] ?? null) ??
218
- (isStagingCli ? null : resolveConfiguredControllerUrl());
241
+ (isStagingCli ? null : resolveConfiguredControllerUrl({ profile }));
219
242
  const defaultLocalControllerUrl = "http://127.0.0.1:8788";
220
243
  const defaultHostedControllerUrl = "https://controller.instafy.dev";
221
244
  const controllerUrl = explicitControllerUrl ??
@@ -226,12 +249,16 @@ export async function login(options) {
226
249
  : defaultHostedControllerUrl);
227
250
  const studioUrl = normalizeUrl(options.studioUrl ?? null) ??
228
251
  normalizeUrl(process.env["INSTAFY_STUDIO_URL"] ?? null) ??
229
- (isStagingCli ? null : resolveConfiguredStudioUrl()) ??
230
- deriveDefaultStudioUrl(controllerUrl);
252
+ (isStagingCli ? null : resolveConfiguredStudioUrl({ profile })) ??
253
+ (await resolveDefaultStudioUrl(controllerUrl));
231
254
  const url = new URL("/cli/login", studioUrl);
232
255
  url.searchParams.set("serverUrl", controllerUrl);
233
256
  if (options.json) {
234
- console.log(JSON.stringify({ url: url.toString(), configPath: getInstafyConfigPath() }));
257
+ console.log(JSON.stringify({
258
+ url: url.toString(),
259
+ profile,
260
+ configPath: profile ? getInstafyProfileConfigPath(profile) : getInstafyConfigPath(),
261
+ }));
235
262
  return;
236
263
  }
237
264
  let callbackServer = null;
@@ -259,7 +286,7 @@ export async function login(options) {
259
286
  console.log("2) After you sign in, copy the token shown on that page.");
260
287
  }
261
288
  console.log("");
262
- const existing = resolveUserAccessToken();
289
+ const existing = resolveUserAccessToken({ profile });
263
290
  let token = provided;
264
291
  if (!token && callbackServer) {
265
292
  if (input.isTTY) {
@@ -337,9 +364,14 @@ export async function login(options) {
337
364
  throw new Error("No token provided.");
338
365
  }
339
366
  if (!options.noStore) {
340
- writeInstafyCliConfig({ controllerUrl, studioUrl, accessToken: token });
367
+ if (profile) {
368
+ writeInstafyProfileConfig(profile, { controllerUrl, studioUrl, accessToken: token });
369
+ }
370
+ else {
371
+ writeInstafyCliConfig({ controllerUrl, studioUrl, accessToken: token });
372
+ }
341
373
  console.log("");
342
- console.log(kleur.green(`Saved token to ${getInstafyConfigPath()}`));
374
+ console.log(kleur.green(`Saved token to ${profile ? getInstafyProfileConfigPath(profile) : getInstafyConfigPath()}`));
343
375
  if (options.gitSetup !== false) {
344
376
  try {
345
377
  const result = installGitCredentialHelper();
@@ -362,16 +394,26 @@ export async function login(options) {
362
394
  console.log(`- ${kleur.cyan("instafy runtime:start")}`);
363
395
  }
364
396
  export async function logout(options) {
365
- clearInstafyCliConfig(["accessToken"]);
366
- try {
367
- uninstallGitCredentialHelper();
397
+ if (options?.profile) {
398
+ clearInstafyProfileConfig(options.profile, ["accessToken"]);
368
399
  }
369
- catch {
370
- // ignore git helper cleanup failures
400
+ else {
401
+ clearInstafyCliConfig(["accessToken"]);
402
+ try {
403
+ uninstallGitCredentialHelper();
404
+ }
405
+ catch {
406
+ // ignore git helper cleanup failures
407
+ }
371
408
  }
372
409
  if (options?.json) {
373
410
  console.log(JSON.stringify({ ok: true }));
374
411
  return;
375
412
  }
376
- console.log(kleur.green("Logged out (cleared saved access token)."));
413
+ if (options?.profile) {
414
+ console.log(kleur.green(`Logged out (cleared saved access token for profile "${options.profile}").`));
415
+ }
416
+ else {
417
+ console.log(kleur.green("Logged out (cleared saved access token)."));
418
+ }
377
419
  }
package/dist/config.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
+ import { findProjectManifest } from "./project-manifest.js";
4
5
  const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
5
6
  const CONFIG_PATH = path.join(INSTAFY_DIR, "config.json");
7
+ const PROFILES_DIR = path.join(INSTAFY_DIR, "profiles");
6
8
  function normalizeToken(value) {
7
9
  if (typeof value !== "string") {
8
10
  return null;
@@ -24,9 +26,45 @@ function normalizeUrl(value) {
24
26
  }
25
27
  return trimmed.replace(/\/$/, "");
26
28
  }
29
+ function normalizeProfileName(value) {
30
+ const trimmed = typeof value === "string" ? value.trim() : "";
31
+ if (!trimmed) {
32
+ return null;
33
+ }
34
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(trimmed)) {
35
+ throw new Error(`Invalid profile name "${value}". Use letters/numbers plus . _ - (max 64 chars).`);
36
+ }
37
+ if (trimmed.includes("..")) {
38
+ throw new Error(`Invalid profile name "${value}".`);
39
+ }
40
+ return trimmed;
41
+ }
27
42
  export function getInstafyConfigPath() {
28
43
  return CONFIG_PATH;
29
44
  }
45
+ export function getInstafyProfilesDirPath() {
46
+ return PROFILES_DIR;
47
+ }
48
+ export function getInstafyProfileConfigPath(profile) {
49
+ const normalized = normalizeProfileName(profile);
50
+ if (!normalized) {
51
+ throw new Error("Profile name is required.");
52
+ }
53
+ return path.join(PROFILES_DIR, `${normalized}.json`);
54
+ }
55
+ export function listInstafyProfileNames() {
56
+ try {
57
+ const entries = fs.readdirSync(PROFILES_DIR, { withFileTypes: true });
58
+ return entries
59
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
60
+ .map((entry) => entry.name.slice(0, -".json".length))
61
+ .filter(Boolean)
62
+ .sort((a, b) => a.localeCompare(b));
63
+ }
64
+ catch {
65
+ return [];
66
+ }
67
+ }
30
68
  export function readInstafyCliConfig() {
31
69
  try {
32
70
  const raw = fs.readFileSync(CONFIG_PATH, "utf8");
@@ -46,6 +84,26 @@ export function readInstafyCliConfig() {
46
84
  return {};
47
85
  }
48
86
  }
87
+ export function readInstafyProfileConfig(profile) {
88
+ const filePath = getInstafyProfileConfigPath(profile);
89
+ try {
90
+ const raw = fs.readFileSync(filePath, "utf8");
91
+ const parsed = JSON.parse(raw);
92
+ if (!parsed || typeof parsed !== "object") {
93
+ return {};
94
+ }
95
+ const record = parsed;
96
+ return {
97
+ controllerUrl: normalizeUrl(typeof record.controllerUrl === "string" ? record.controllerUrl : null),
98
+ studioUrl: normalizeUrl(typeof record.studioUrl === "string" ? record.studioUrl : null),
99
+ accessToken: normalizeToken(typeof record.accessToken === "string" ? record.accessToken : null),
100
+ updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : null,
101
+ };
102
+ }
103
+ catch {
104
+ return {};
105
+ }
106
+ }
49
107
  export function writeInstafyCliConfig(update) {
50
108
  const existing = readInstafyCliConfig();
51
109
  const next = {
@@ -64,6 +122,25 @@ export function writeInstafyCliConfig(update) {
64
122
  }
65
123
  return next;
66
124
  }
125
+ export function writeInstafyProfileConfig(profile, update) {
126
+ const filePath = getInstafyProfileConfigPath(profile);
127
+ const existing = readInstafyProfileConfig(profile);
128
+ const next = {
129
+ controllerUrl: normalizeUrl(update.controllerUrl ?? existing.controllerUrl ?? null),
130
+ studioUrl: normalizeUrl(update.studioUrl ?? existing.studioUrl ?? null),
131
+ accessToken: normalizeToken(update.accessToken ?? existing.accessToken ?? null),
132
+ updatedAt: new Date().toISOString(),
133
+ };
134
+ fs.mkdirSync(PROFILES_DIR, { recursive: true });
135
+ fs.writeFileSync(filePath, JSON.stringify(next, null, 2), { encoding: "utf8" });
136
+ try {
137
+ fs.chmodSync(filePath, 0o600);
138
+ }
139
+ catch {
140
+ // ignore chmod failures (windows / unusual fs)
141
+ }
142
+ return next;
143
+ }
67
144
  export function clearInstafyCliConfig(keys) {
68
145
  if (!keys || keys.length === 0) {
69
146
  try {
@@ -81,20 +158,70 @@ export function clearInstafyCliConfig(keys) {
81
158
  }
82
159
  writeInstafyCliConfig(next);
83
160
  }
84
- export function resolveConfiguredControllerUrl() {
161
+ export function clearInstafyProfileConfig(profile, keys) {
162
+ const filePath = getInstafyProfileConfigPath(profile);
163
+ if (!keys || keys.length === 0) {
164
+ try {
165
+ fs.rmSync(filePath, { force: true });
166
+ }
167
+ catch {
168
+ // ignore
169
+ }
170
+ return;
171
+ }
172
+ const existing = readInstafyProfileConfig(profile);
173
+ const next = { ...existing };
174
+ for (const key of keys) {
175
+ next[key] = null;
176
+ }
177
+ writeInstafyProfileConfig(profile, next);
178
+ }
179
+ export function resolveActiveProfileName(params) {
180
+ const explicit = normalizeProfileName(params?.profile ?? null);
181
+ if (explicit) {
182
+ return explicit;
183
+ }
184
+ const fromEnv = normalizeProfileName(process.env["INSTAFY_PROFILE"] ?? null);
185
+ if (fromEnv) {
186
+ return fromEnv;
187
+ }
188
+ const cwd = (params?.cwd ?? process.cwd()).trim();
189
+ if (!cwd) {
190
+ return null;
191
+ }
192
+ const manifest = findProjectManifest(cwd).manifest;
193
+ return normalizeProfileName(manifest?.profile ?? null);
194
+ }
195
+ export function resolveConfiguredControllerUrl(params) {
196
+ const profile = resolveActiveProfileName(params);
197
+ if (profile) {
198
+ const configured = readInstafyProfileConfig(profile);
199
+ return normalizeUrl(configured.controllerUrl ?? null);
200
+ }
85
201
  const config = readInstafyCliConfig();
86
202
  return normalizeUrl(config.controllerUrl ?? null);
87
203
  }
88
- export function resolveConfiguredStudioUrl() {
204
+ export function resolveConfiguredStudioUrl(params) {
205
+ const profile = resolveActiveProfileName(params);
206
+ if (profile) {
207
+ const configured = readInstafyProfileConfig(profile);
208
+ return normalizeUrl(configured.studioUrl ?? null);
209
+ }
89
210
  const config = readInstafyCliConfig();
90
211
  return normalizeUrl(config.studioUrl ?? null);
91
212
  }
92
- export function resolveConfiguredAccessToken() {
213
+ export function resolveConfiguredAccessToken(params) {
214
+ const profile = resolveActiveProfileName(params);
215
+ if (profile) {
216
+ const configured = readInstafyProfileConfig(profile);
217
+ return normalizeToken(configured.accessToken ?? null);
218
+ }
93
219
  const config = readInstafyCliConfig();
94
220
  return normalizeToken(config.accessToken ?? null);
95
221
  }
96
222
  export function resolveControllerUrl(params) {
97
- const config = readInstafyCliConfig();
223
+ const profile = resolveActiveProfileName({ profile: params?.profile ?? null, cwd: params?.cwd ?? null });
224
+ const config = profile ? readInstafyProfileConfig(profile) : readInstafyCliConfig();
98
225
  return (normalizeUrl(params?.controllerUrl ?? null) ??
99
226
  normalizeUrl(process.env["INSTAFY_SERVER_URL"] ?? null) ??
100
227
  normalizeUrl(process.env["CONTROLLER_BASE_URL"] ?? null) ??
@@ -102,7 +229,8 @@ export function resolveControllerUrl(params) {
102
229
  "http://127.0.0.1:8788");
103
230
  }
104
231
  export function resolveUserAccessToken(params) {
105
- const config = readInstafyCliConfig();
232
+ const profile = resolveActiveProfileName({ profile: params?.profile ?? null, cwd: params?.cwd ?? null });
233
+ const config = profile ? readInstafyProfileConfig(profile) : readInstafyCliConfig();
106
234
  return (normalizeToken(params?.accessToken ?? null) ??
107
235
  normalizeToken(process.env["INSTAFY_ACCESS_TOKEN"] ?? null) ??
108
236
  normalizeToken(process.env["CONTROLLER_ACCESS_TOKEN"] ?? null) ??
package/dist/git.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import kleur from "kleur";
4
- import { resolveConfiguredAccessToken } from "./config.js";
4
+ import { resolveConfiguredAccessToken, resolveControllerUrl } from "./config.js";
5
5
  function normalizeToken(value) {
6
6
  if (typeof value !== "string") {
7
7
  return null;
@@ -68,10 +68,7 @@ export async function mintGitAccessToken(params) {
68
68
  };
69
69
  }
70
70
  export async function gitToken(options) {
71
- const controllerUrl = options.controllerUrl ??
72
- process.env["INSTAFY_SERVER_URL"] ??
73
- process.env["CONTROLLER_BASE_URL"] ??
74
- "http://127.0.0.1:8788";
71
+ const controllerUrl = resolveControllerUrl({ controllerUrl: options.controllerUrl ?? null });
75
72
  const token = resolveControllerAccessToken({
76
73
  controllerAccessToken: options.controllerAccessToken,
77
74
  supabaseAccessToken: options.supabaseAccessToken,
package/dist/index.js CHANGED
@@ -6,10 +6,11 @@ import { runtimeStart, runtimeStatus, runtimeStop, runtimeToken, findProjectMani
6
6
  import { login, logout } from "./auth.js";
7
7
  import { gitToken } from "./git.js";
8
8
  import { runGitCredentialHelper } from "./git-credential.js";
9
- import { projectInit } from "./project.js";
9
+ import { projectInit, projectProfile } from "./project.js";
10
10
  import { listTunnelSessions, startTunnelDetached, stopTunnelSession, tailTunnelLogs, runTunnelCommand } from "./tunnel.js";
11
11
  import { requestControllerApi } from "./api.js";
12
12
  import { configGet, configList, configPath, configSet, configUnset } from "./config-command.js";
13
+ import { getInstafyProfileConfigPath, listInstafyProfileNames, readInstafyProfileConfig } from "./config.js";
13
14
  export const program = new Command();
14
15
  const require = createRequire(import.meta.url);
15
16
  const pkg = require("../package.json");
@@ -40,6 +41,7 @@ program
40
41
  .description("Log in and save an access token for future CLI commands")
41
42
  .option("--studio-url <url>", "Studio web URL (default: staging or localhost)")
42
43
  .option("--server-url <url>", "Instafy server/controller URL")
44
+ .option("--profile <name>", "Save token under a named profile (multi-account support)")
43
45
  .option("--token <token>", "Provide token directly (skips prompt)")
44
46
  .option("--no-git-setup", "Do not configure git credential helper")
45
47
  .option("--no-store", "Do not save token to ~/.instafy/config.json")
@@ -52,6 +54,7 @@ program
52
54
  token: opts.token,
53
55
  gitSetup: opts.gitSetup,
54
56
  noStore: opts.store === false,
57
+ profile: opts.profile,
55
58
  json: opts.json,
56
59
  });
57
60
  }
@@ -63,10 +66,11 @@ program
63
66
  program
64
67
  .command("logout")
65
68
  .description("Clear the saved CLI access token")
69
+ .option("--profile <name>", "Clear the token for a named profile")
66
70
  .option("--json", "Output JSON")
67
71
  .action(async (opts) => {
68
72
  try {
69
- await logout({ json: opts.json });
73
+ await logout({ json: opts.json, profile: opts.profile });
70
74
  }
71
75
  catch (error) {
72
76
  console.error(kleur.red(String(error)));
@@ -80,6 +84,7 @@ const projectInitCommand = program
80
84
  addServerUrlOptions(projectInitCommand);
81
85
  projectInitCommand
82
86
  .option("--access-token <token>", "Instafy or Supabase access token")
87
+ .option("--profile <name>", "CLI profile to use when running commands in this folder")
83
88
  .option("--project-type <type>", "Project type (customer|sandbox)")
84
89
  .option("--org-id <uuid>", "Optional organization id")
85
90
  .option("--org-name <name>", "Optional organization name")
@@ -92,6 +97,7 @@ projectInitCommand
92
97
  path: opts.path,
93
98
  controllerUrl: opts.serverUrl ?? opts.controllerUrl,
94
99
  accessToken: opts.accessToken,
100
+ profile: opts.profile,
95
101
  projectType: opts.projectType,
96
102
  orgId: opts.orgId,
97
103
  orgName: opts.orgName,
@@ -105,6 +111,65 @@ projectInitCommand
105
111
  process.exit(1);
106
112
  }
107
113
  });
114
+ program
115
+ .command("project:profile")
116
+ .description("Get/set the CLI profile for this folder (.instafy/project.json)")
117
+ .argument("[profile]", "Profile name to set (omit to print current)")
118
+ .option("--unset", "Clear the configured profile for this folder")
119
+ .option("--path <dir>", "Directory to search for the manifest (default: cwd)")
120
+ .option("--json", "Output JSON")
121
+ .action(async (profile, opts) => {
122
+ try {
123
+ projectProfile({
124
+ profile,
125
+ unset: opts.unset,
126
+ path: opts.path,
127
+ json: opts.json,
128
+ });
129
+ }
130
+ catch (error) {
131
+ console.error(kleur.red(String(error)));
132
+ process.exit(1);
133
+ }
134
+ });
135
+ program
136
+ .command("profile:list")
137
+ .description("List saved CLI profiles (~/.instafy/profiles)")
138
+ .option("--json", "Output JSON")
139
+ .action(async (opts) => {
140
+ try {
141
+ const names = listInstafyProfileNames();
142
+ const profiles = names.map((name) => {
143
+ const config = readInstafyProfileConfig(name);
144
+ return {
145
+ name,
146
+ path: getInstafyProfileConfigPath(name),
147
+ controllerUrl: config.controllerUrl ?? null,
148
+ studioUrl: config.studioUrl ?? null,
149
+ accessTokenSet: Boolean(config.accessToken),
150
+ updatedAt: config.updatedAt ?? null,
151
+ };
152
+ });
153
+ if (opts.json) {
154
+ console.log(JSON.stringify({ profiles }, null, 2));
155
+ return;
156
+ }
157
+ if (profiles.length === 0) {
158
+ console.log(kleur.yellow("No profiles found."));
159
+ console.log(`Create one with: ${kleur.cyan("instafy login --profile <name>")}`);
160
+ return;
161
+ }
162
+ console.log(kleur.green("Instafy CLI profiles"));
163
+ for (const profile of profiles) {
164
+ const token = profile.accessTokenSet ? kleur.green("token") : kleur.yellow("no-token");
165
+ console.log(`- ${kleur.cyan(profile.name)} (${token})`);
166
+ }
167
+ }
168
+ catch (error) {
169
+ console.error(kleur.red(String(error)));
170
+ process.exit(1);
171
+ }
172
+ });
108
173
  const configCommand = program.command("config").description("Get/set saved CLI configuration");
109
174
  configCommand
110
175
  .command("path")
@@ -274,8 +339,8 @@ runtimeTokenCommand
274
339
  }
275
340
  });
276
341
  const gitTokenCommand = program
277
- .command("git:token")
278
- .description("Mint a git access token for the project repo")
342
+ .command("git:token", { hidden: true })
343
+ .description("Advanced: mint a git access token for the project repo")
279
344
  .option("--project <id>", "Project UUID (defaults to .instafy/project.json)");
280
345
  addServerUrlOptions(gitTokenCommand);
281
346
  addAccessTokenOptions(gitTokenCommand, "Instafy access token (required)");
@@ -536,22 +601,22 @@ function configureApiCommand(command, method) {
536
601
  });
537
602
  }
538
603
  const apiGetCommand = program
539
- .command("api:get")
604
+ .command("api:get", { hidden: true })
540
605
  .description("Advanced: authenticated GET request to the controller API")
541
606
  .argument("<path>", "API path (or full URL), e.g. /conversations/<id>/messages?limit=50");
542
607
  configureApiCommand(apiGetCommand, "GET");
543
608
  const apiPostCommand = program
544
- .command("api:post")
609
+ .command("api:post", { hidden: true })
545
610
  .description("Advanced: authenticated POST request to the controller API")
546
611
  .argument("<path>", "API path (or full URL)");
547
612
  configureApiCommand(apiPostCommand, "POST");
548
613
  const apiPatchCommand = program
549
- .command("api:patch")
614
+ .command("api:patch", { hidden: true })
550
615
  .description("Advanced: authenticated PATCH request to the controller API")
551
616
  .argument("<path>", "API path (or full URL)");
552
617
  configureApiCommand(apiPatchCommand, "PATCH");
553
618
  const apiDeleteCommand = program
554
- .command("api:delete")
619
+ .command("api:delete", { hidden: true })
555
620
  .description("Advanced: authenticated DELETE request to the controller API")
556
621
  .argument("<path>", "API path (or full URL)");
557
622
  configureApiCommand(apiDeleteCommand, "DELETE");
@@ -0,0 +1,24 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ export function findProjectManifest(startDir) {
4
+ let current = path.resolve(startDir);
5
+ const root = path.parse(current).root;
6
+ while (true) {
7
+ const candidate = path.join(current, ".instafy", "project.json");
8
+ if (existsSync(candidate)) {
9
+ try {
10
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
11
+ if (parsed?.projectId) {
12
+ return { manifest: parsed, path: candidate };
13
+ }
14
+ }
15
+ catch {
16
+ // ignore malformed manifest
17
+ }
18
+ }
19
+ if (current === root)
20
+ break;
21
+ current = path.dirname(current);
22
+ }
23
+ return { manifest: null, path: null };
24
+ }
package/dist/project.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import kleur from "kleur";
4
- import { resolveControllerUrl, resolveUserAccessToken } from "./config.js";
4
+ import { getInstafyProfileConfigPath, resolveControllerUrl, resolveUserAccessToken } from "./config.js";
5
5
  import { formatAuthRequiredError } from "./errors.js";
6
+ import { findProjectManifest } from "./project-manifest.js";
6
7
  async function fetchOrganizations(controllerUrl, token) {
7
8
  const response = await fetch(`${controllerUrl}/orgs`, {
8
9
  headers: {
@@ -111,8 +112,16 @@ export async function listProjects(options) {
111
112
  }
112
113
  export async function projectInit(options) {
113
114
  const rootDir = path.resolve(options.path ?? process.cwd());
114
- const controllerUrl = resolveControllerUrl({ controllerUrl: options.controllerUrl ?? null });
115
- const token = resolveUserAccessToken({ accessToken: options.accessToken ?? null });
115
+ const controllerUrl = resolveControllerUrl({
116
+ controllerUrl: options.controllerUrl ?? null,
117
+ profile: options.profile ?? null,
118
+ cwd: rootDir,
119
+ });
120
+ const token = resolveUserAccessToken({
121
+ accessToken: options.accessToken ?? null,
122
+ profile: options.profile ?? null,
123
+ cwd: rootDir,
124
+ });
116
125
  if (!token) {
117
126
  throw formatAuthRequiredError({
118
127
  retryCommand: "instafy project:init",
@@ -146,6 +155,7 @@ export async function projectInit(options) {
146
155
  orgId: json.org_id ?? org.orgId ?? null,
147
156
  orgName: json.org_name ?? org.orgName ?? null,
148
157
  controllerUrl,
158
+ profile: options.profile ?? null,
149
159
  createdAt: new Date().toISOString(),
150
160
  }, null, 2), "utf8");
151
161
  }
@@ -171,3 +181,47 @@ export async function projectInit(options) {
171
181
  }
172
182
  return json;
173
183
  }
184
+ export function projectProfile(options) {
185
+ const rootDir = path.resolve(options.path ?? process.cwd());
186
+ const manifestInfo = findProjectManifest(rootDir);
187
+ if (!manifestInfo.path || !manifestInfo.manifest) {
188
+ throw new Error("No project configured. Run `instafy project:init` in this folder first.");
189
+ }
190
+ const shouldUpdate = options.unset === true || typeof options.profile === "string";
191
+ const currentProfile = typeof manifestInfo.manifest.profile === "string" && manifestInfo.manifest.profile.trim()
192
+ ? manifestInfo.manifest.profile.trim()
193
+ : null;
194
+ if (!shouldUpdate) {
195
+ if (options.json) {
196
+ console.log(JSON.stringify({ ok: true, path: manifestInfo.path, profile: currentProfile }, null, 2));
197
+ }
198
+ else {
199
+ console.log(kleur.green("Project profile"));
200
+ console.log(`Manifest: ${manifestInfo.path}`);
201
+ console.log(`Profile: ${currentProfile ?? kleur.yellow("(none)")}`);
202
+ }
203
+ return { path: manifestInfo.path, profile: currentProfile };
204
+ }
205
+ const nextProfileRaw = options.unset === true ? null : typeof options.profile === "string" ? options.profile.trim() : null;
206
+ const nextProfile = nextProfileRaw && nextProfileRaw.length > 0 ? nextProfileRaw : null;
207
+ if (nextProfile) {
208
+ getInstafyProfileConfigPath(nextProfile);
209
+ }
210
+ const updated = { ...manifestInfo.manifest };
211
+ if (nextProfile) {
212
+ updated.profile = nextProfile;
213
+ }
214
+ else {
215
+ delete updated.profile;
216
+ }
217
+ fs.writeFileSync(manifestInfo.path, JSON.stringify(updated, null, 2), "utf8");
218
+ if (options.json) {
219
+ console.log(JSON.stringify({ ok: true, path: manifestInfo.path, profile: nextProfile }, null, 2));
220
+ }
221
+ else {
222
+ console.log(kleur.green("Updated project profile."));
223
+ console.log(`Manifest: ${manifestInfo.path}`);
224
+ console.log(`Profile: ${nextProfile ?? kleur.yellow("(none)")}`);
225
+ }
226
+ return { path: manifestInfo.path, profile: nextProfile };
227
+ }
package/dist/runtime.js CHANGED
@@ -7,6 +7,7 @@ import { randomUUID } from "node:crypto";
7
7
  import os from "node:os";
8
8
  import { ensureRatholeBinary } from "./rathole.js";
9
9
  import { resolveConfiguredAccessToken } from "./config.js";
10
+ import { findProjectManifest } from "./project-manifest.js";
10
11
  const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
11
12
  const STATE_FILE = path.join(INSTAFY_DIR, "cli-runtime-state.json");
12
13
  const LOG_DIR = path.join(INSTAFY_DIR, "cli-runtime-logs");
@@ -142,28 +143,7 @@ function normalizeToken(value) {
142
143
  const trimmed = value.trim();
143
144
  return trimmed.length > 0 ? trimmed : null;
144
145
  }
145
- export function findProjectManifest(startDir) {
146
- let current = path.resolve(startDir);
147
- const root = path.parse(current).root;
148
- while (true) {
149
- const candidate = path.join(current, ".instafy", "project.json");
150
- if (existsSync(candidate)) {
151
- try {
152
- const parsed = JSON.parse(readFileSync(candidate, "utf8"));
153
- if (parsed?.projectId) {
154
- return { manifest: parsed, path: candidate };
155
- }
156
- }
157
- catch {
158
- // ignore malformed manifest
159
- }
160
- }
161
- if (current === root)
162
- break;
163
- current = path.dirname(current);
164
- }
165
- return { manifest: null, path: null };
166
- }
146
+ export { findProjectManifest };
167
147
  function readTokenFromFile(filePath) {
168
148
  const normalized = normalizeToken(filePath);
169
149
  if (!normalized) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instafy/cli",
3
- "version": "0.1.8-staging.357",
3
+ "version": "0.1.8-staging.365",
4
4
  "description": "Run Instafy projects locally, link folders to Studio, and share previews/webhooks via tunnels.",
5
5
  "private": false,
6
6
  "publishConfig": {