@instafy/cli 0.1.8-staging.364 → 0.1.8-staging.366
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 +1 -0
- package/dist/auth.js +27 -5
- package/dist/git-wrapper.js +85 -0
- package/dist/index.js +55 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ Run Instafy projects locally and connect them back to Instafy Studio — from an
|
|
|
24
24
|
- `instafy runtime:start` — start a local runtime (agent + origin).
|
|
25
25
|
- `instafy runtime:status` — show health of the last started runtime.
|
|
26
26
|
- `instafy runtime:stop` — stop the last started runtime.
|
|
27
|
+
- `instafy git <args...>` — run git commands against an Instafy canonical checkout (`.instafy/.git`) when present.
|
|
27
28
|
- `instafy tunnel` — start a detached tunnel for a local port.
|
|
28
29
|
- `instafy tunnel:list` — list local tunnels started by the CLI.
|
|
29
30
|
- `instafy tunnel:logs <tunnelId> --follow` — tail tunnel logs.
|
package/dist/auth.js
CHANGED
|
@@ -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
|
|
49
|
-
|
|
50
|
-
|
|
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;
|
|
68
|
+
}
|
|
69
|
+
if (!looksLikeLocalControllerUrl(controllerUrl)) {
|
|
70
|
+
return hosted;
|
|
51
71
|
}
|
|
52
|
-
|
|
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`;
|
|
@@ -228,7 +250,7 @@ export async function login(options) {
|
|
|
228
250
|
const studioUrl = normalizeUrl(options.studioUrl ?? null) ??
|
|
229
251
|
normalizeUrl(process.env["INSTAFY_STUDIO_URL"] ?? null) ??
|
|
230
252
|
(isStagingCli ? null : resolveConfiguredStudioUrl({ profile })) ??
|
|
231
|
-
|
|
253
|
+
(await resolveDefaultStudioUrl(controllerUrl));
|
|
232
254
|
const url = new URL("/cli/login", studioUrl);
|
|
233
255
|
url.searchParams.set("serverUrl", controllerUrl);
|
|
234
256
|
if (options.json) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import kleur from "kleur";
|
|
5
|
+
function pathExists(candidate) {
|
|
6
|
+
try {
|
|
7
|
+
fs.statSync(candidate);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function findInstafyGitContext(startDir) {
|
|
15
|
+
let current = path.resolve(startDir);
|
|
16
|
+
const root = path.parse(current).root;
|
|
17
|
+
while (true) {
|
|
18
|
+
const gitDir = path.join(current, ".instafy", ".git");
|
|
19
|
+
if (pathExists(gitDir)) {
|
|
20
|
+
return { workTree: current, gitDir };
|
|
21
|
+
}
|
|
22
|
+
if (current === root)
|
|
23
|
+
break;
|
|
24
|
+
current = path.dirname(current);
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function validateGitArgs(userArgs) {
|
|
29
|
+
let parsingGlobalOptions = true;
|
|
30
|
+
for (let index = 0; index < userArgs.length; index += 1) {
|
|
31
|
+
const arg = userArgs[index] ?? "";
|
|
32
|
+
if (!parsingGlobalOptions) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg === "--") {
|
|
36
|
+
parsingGlobalOptions = false;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
40
|
+
parsingGlobalOptions = false;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === "--git-dir" || arg.startsWith("--git-dir=") || arg === "--work-tree" || arg.startsWith("--work-tree=") || arg === "-C") {
|
|
44
|
+
throw new Error(`Unsupported argument "${arg}". Use ${kleur.cyan("cd")} instead; ${kleur.cyan("instafy git")} manages --git-dir/--work-tree automatically.`);
|
|
45
|
+
}
|
|
46
|
+
// Skip the value for global options that consume the next token.
|
|
47
|
+
if (arg === "-c" || arg === "--config-env" || arg === "--exec-path" || arg === "--namespace") {
|
|
48
|
+
index += 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function buildInstafyGitArgs(context, userArgs) {
|
|
53
|
+
validateGitArgs(userArgs);
|
|
54
|
+
return ["--git-dir", context.gitDir, "--work-tree", context.workTree, ...userArgs];
|
|
55
|
+
}
|
|
56
|
+
export function runInstafyGit(userArgs, options) {
|
|
57
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
58
|
+
if (userArgs.length === 0 || userArgs[0] === "--help" || userArgs[0] === "-h") {
|
|
59
|
+
// Keep this minimal so it doesn't diverge from Git help output.
|
|
60
|
+
console.log("instafy git <git-args...>");
|
|
61
|
+
console.log("");
|
|
62
|
+
console.log(`Runs ${kleur.cyan("git")} against the Instafy canonical repo at ${kleur.cyan(".instafy/.git")} (auto-detected by walking up from cwd).`);
|
|
63
|
+
console.log("");
|
|
64
|
+
console.log("Example:");
|
|
65
|
+
console.log(` ${kleur.cyan('instafy git status')}`);
|
|
66
|
+
console.log(` ${kleur.cyan('instafy git commit -am "instafy: checkpoint"')}`);
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
const context = findInstafyGitContext(cwd);
|
|
70
|
+
if (!context) {
|
|
71
|
+
throw new Error([
|
|
72
|
+
"No Instafy canonical git checkout found (expected .instafy/.git).",
|
|
73
|
+
"",
|
|
74
|
+
"Tips:",
|
|
75
|
+
`- Run this inside a git-canonical workspace (where ${kleur.cyan(".instafy/.git")} exists).`,
|
|
76
|
+
`- If you're trying to operate on a user repo, use normal ${kleur.cyan("git")} (it uses .git).`,
|
|
77
|
+
].join("\n"));
|
|
78
|
+
}
|
|
79
|
+
const args = buildInstafyGitArgs(context, userArgs);
|
|
80
|
+
const result = spawnSync("git", args, { stdio: "inherit", cwd });
|
|
81
|
+
if (result.error) {
|
|
82
|
+
throw new Error(`Failed to run git: ${result.error.message}`);
|
|
83
|
+
}
|
|
84
|
+
return typeof result.status === "number" ? result.status : 1;
|
|
85
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { listTunnelSessions, startTunnelDetached, stopTunnelSession, tailTunnelL
|
|
|
11
11
|
import { requestControllerApi } from "./api.js";
|
|
12
12
|
import { configGet, configList, configPath, configSet, configUnset } from "./config-command.js";
|
|
13
13
|
import { getInstafyProfileConfigPath, listInstafyProfileNames, readInstafyProfileConfig } from "./config.js";
|
|
14
|
+
import { runInstafyGit } from "./git-wrapper.js";
|
|
14
15
|
export const program = new Command();
|
|
15
16
|
const require = createRequire(import.meta.url);
|
|
16
17
|
const pkg = require("../package.json");
|
|
@@ -77,44 +78,6 @@ program
|
|
|
77
78
|
process.exit(1);
|
|
78
79
|
}
|
|
79
80
|
});
|
|
80
|
-
program
|
|
81
|
-
.command("profile:list")
|
|
82
|
-
.description("List saved CLI profiles (~/.instafy/profiles)")
|
|
83
|
-
.option("--json", "Output JSON")
|
|
84
|
-
.action(async (opts) => {
|
|
85
|
-
try {
|
|
86
|
-
const names = listInstafyProfileNames();
|
|
87
|
-
const profiles = names.map((name) => {
|
|
88
|
-
const config = readInstafyProfileConfig(name);
|
|
89
|
-
return {
|
|
90
|
-
name,
|
|
91
|
-
path: getInstafyProfileConfigPath(name),
|
|
92
|
-
controllerUrl: config.controllerUrl ?? null,
|
|
93
|
-
studioUrl: config.studioUrl ?? null,
|
|
94
|
-
accessTokenSet: Boolean(config.accessToken),
|
|
95
|
-
updatedAt: config.updatedAt ?? null,
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
if (opts.json) {
|
|
99
|
-
console.log(JSON.stringify({ profiles }, null, 2));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (profiles.length === 0) {
|
|
103
|
-
console.log(kleur.yellow("No profiles found."));
|
|
104
|
-
console.log(`Create one with: ${kleur.cyan("instafy login --profile <name>")}`);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
console.log(kleur.green("Instafy CLI profiles"));
|
|
108
|
-
for (const profile of profiles) {
|
|
109
|
-
const token = profile.accessTokenSet ? kleur.green("token") : kleur.yellow("no-token");
|
|
110
|
-
console.log(`- ${kleur.cyan(profile.name)} (${token})`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
console.error(kleur.red(String(error)));
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
81
|
const projectInitCommand = program
|
|
119
82
|
.command("project:init")
|
|
120
83
|
.description("Create an Instafy project and link this folder (.instafy/project.json)")
|
|
@@ -170,6 +133,44 @@ program
|
|
|
170
133
|
process.exit(1);
|
|
171
134
|
}
|
|
172
135
|
});
|
|
136
|
+
program
|
|
137
|
+
.command("profile:list")
|
|
138
|
+
.description("List saved CLI profiles (~/.instafy/profiles)")
|
|
139
|
+
.option("--json", "Output JSON")
|
|
140
|
+
.action(async (opts) => {
|
|
141
|
+
try {
|
|
142
|
+
const names = listInstafyProfileNames();
|
|
143
|
+
const profiles = names.map((name) => {
|
|
144
|
+
const config = readInstafyProfileConfig(name);
|
|
145
|
+
return {
|
|
146
|
+
name,
|
|
147
|
+
path: getInstafyProfileConfigPath(name),
|
|
148
|
+
controllerUrl: config.controllerUrl ?? null,
|
|
149
|
+
studioUrl: config.studioUrl ?? null,
|
|
150
|
+
accessTokenSet: Boolean(config.accessToken),
|
|
151
|
+
updatedAt: config.updatedAt ?? null,
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
if (opts.json) {
|
|
155
|
+
console.log(JSON.stringify({ profiles }, null, 2));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (profiles.length === 0) {
|
|
159
|
+
console.log(kleur.yellow("No profiles found."));
|
|
160
|
+
console.log(`Create one with: ${kleur.cyan("instafy login --profile <name>")}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
console.log(kleur.green("Instafy CLI profiles"));
|
|
164
|
+
for (const profile of profiles) {
|
|
165
|
+
const token = profile.accessTokenSet ? kleur.green("token") : kleur.yellow("no-token");
|
|
166
|
+
console.log(`- ${kleur.cyan(profile.name)} (${token})`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error(kleur.red(String(error)));
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
173
174
|
const configCommand = program.command("config").description("Get/set saved CLI configuration");
|
|
174
175
|
configCommand
|
|
175
176
|
.command("path")
|
|
@@ -339,8 +340,8 @@ runtimeTokenCommand
|
|
|
339
340
|
}
|
|
340
341
|
});
|
|
341
342
|
const gitTokenCommand = program
|
|
342
|
-
.command("git:token")
|
|
343
|
-
.description("
|
|
343
|
+
.command("git:token", { hidden: true })
|
|
344
|
+
.description("Advanced: mint a git access token for the project repo")
|
|
344
345
|
.option("--project <id>", "Project UUID (defaults to .instafy/project.json)");
|
|
345
346
|
addServerUrlOptions(gitTokenCommand);
|
|
346
347
|
addAccessTokenOptions(gitTokenCommand, "Instafy access token (required)");
|
|
@@ -379,6 +380,10 @@ gitTokenCommand
|
|
|
379
380
|
process.exit(1);
|
|
380
381
|
}
|
|
381
382
|
});
|
|
383
|
+
program
|
|
384
|
+
.command("git")
|
|
385
|
+
.description("Run git against the Instafy canonical checkout (.instafy/.git)")
|
|
386
|
+
.allowUnknownOption(true);
|
|
382
387
|
program
|
|
383
388
|
.command("git:credential", { hidden: true })
|
|
384
389
|
.description("Internal: git credential helper (used by Git when configured)")
|
|
@@ -601,22 +606,22 @@ function configureApiCommand(command, method) {
|
|
|
601
606
|
});
|
|
602
607
|
}
|
|
603
608
|
const apiGetCommand = program
|
|
604
|
-
.command("api:get")
|
|
609
|
+
.command("api:get", { hidden: true })
|
|
605
610
|
.description("Advanced: authenticated GET request to the controller API")
|
|
606
611
|
.argument("<path>", "API path (or full URL), e.g. /conversations/<id>/messages?limit=50");
|
|
607
612
|
configureApiCommand(apiGetCommand, "GET");
|
|
608
613
|
const apiPostCommand = program
|
|
609
|
-
.command("api:post")
|
|
614
|
+
.command("api:post", { hidden: true })
|
|
610
615
|
.description("Advanced: authenticated POST request to the controller API")
|
|
611
616
|
.argument("<path>", "API path (or full URL)");
|
|
612
617
|
configureApiCommand(apiPostCommand, "POST");
|
|
613
618
|
const apiPatchCommand = program
|
|
614
|
-
.command("api:patch")
|
|
619
|
+
.command("api:patch", { hidden: true })
|
|
615
620
|
.description("Advanced: authenticated PATCH request to the controller API")
|
|
616
621
|
.argument("<path>", "API path (or full URL)");
|
|
617
622
|
configureApiCommand(apiPatchCommand, "PATCH");
|
|
618
623
|
const apiDeleteCommand = program
|
|
619
|
-
.command("api:delete")
|
|
624
|
+
.command("api:delete", { hidden: true })
|
|
620
625
|
.description("Advanced: authenticated DELETE request to the controller API")
|
|
621
626
|
.argument("<path>", "API path (or full URL)");
|
|
622
627
|
configureApiCommand(apiDeleteCommand, "DELETE");
|
|
@@ -625,6 +630,12 @@ export async function runCli(argv = process.argv) {
|
|
|
625
630
|
program.outputHelp();
|
|
626
631
|
return;
|
|
627
632
|
}
|
|
633
|
+
const args = argv.slice(2);
|
|
634
|
+
if (args[0] === "git") {
|
|
635
|
+
const code = runInstafyGit(args.slice(1));
|
|
636
|
+
process.exitCode = code;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
628
639
|
await program.parseAsync(argv);
|
|
629
640
|
}
|
|
630
641
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
package/package.json
CHANGED