@idapt/cli 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,15 +1,687 @@
1
1
  #!/usr/bin/env node
2
- import { createFetchTransport, execute, autoMode, commandsForResource, findCommand, renderHelp, listResources, renderInstructions, quoteToken } from './chunk-TYSHO65D.js';
3
- import { readFileSync, mkdirSync, writeFileSync, chmodSync, rmSync } from 'fs';
2
+ import { createFetchTransport, execute, autoMode, commandsForResource, findCommand, renderHelp, listResources, renderInstructions, quoteToken, commandCatalog, executeCommand } from './chunk-3CWHRMLZ.js';
3
+ import { readFileSync, mkdirSync, writeFileSync, chmodSync, rmSync, readdirSync, existsSync } from 'fs';
4
+ import { dirname, resolve, join, relative, sep } from 'path';
4
5
  import { spawn } from 'child_process';
5
- import { dirname, join } from 'path';
6
6
  import { homedir } from 'os';
7
7
  import { createServer } from 'http';
8
8
  import { randomBytes, createHash } from 'crypto';
9
9
 
10
10
  // src/version.ts
11
- var VERSION = "1.9.0";
11
+ var VERSION = "1.11.0";
12
12
  var USER_AGENT = `idapt-cli/${VERSION}`;
13
+ var MAX_BUNDLE_FILES = 500;
14
+ var MAX_BUNDLE_FILE_BYTES = 25 * 1024 * 1024;
15
+ var MAX_BUNDLE_TOTAL_BYTES = 100 * 1024 * 1024;
16
+ function parseDeployArgs(args) {
17
+ let dir;
18
+ let app;
19
+ let workspace;
20
+ let name;
21
+ for (let i = 0; i < args.length; i++) {
22
+ const a = args[i];
23
+ if (a === "--app") app = args[++i];
24
+ else if (a.startsWith("--app=")) app = a.slice("--app=".length);
25
+ else if (a === "--workspace" || a === "--workspace-id")
26
+ workspace = args[++i];
27
+ else if (a.startsWith("--workspace="))
28
+ workspace = a.slice("--workspace=".length);
29
+ else if (a.startsWith("--workspace-id="))
30
+ workspace = a.slice("--workspace-id=".length);
31
+ else if (a === "--name") name = args[++i];
32
+ else if (a.startsWith("--name=")) name = a.slice("--name=".length);
33
+ else if (!a.startsWith("-") && dir === void 0) dir = a;
34
+ }
35
+ return { dir: dir ?? "", app, workspace, name };
36
+ }
37
+ function buildDeployPayload(files) {
38
+ if (files.length === 0) {
39
+ return { ok: false, error: "no files found to deploy" };
40
+ }
41
+ if (files.length > MAX_BUNDLE_FILES) {
42
+ return {
43
+ ok: false,
44
+ error: `too many files: ${files.length} (max ${MAX_BUNDLE_FILES})`
45
+ };
46
+ }
47
+ const out = [];
48
+ let totalBytes = 0;
49
+ for (const f of files) {
50
+ const size = f.bytes.byteLength;
51
+ if (size > MAX_BUNDLE_FILE_BYTES) {
52
+ return {
53
+ ok: false,
54
+ error: `file too large: ${f.path} (${size} bytes, max ${MAX_BUNDLE_FILE_BYTES})`
55
+ };
56
+ }
57
+ totalBytes += size;
58
+ if (totalBytes > MAX_BUNDLE_TOTAL_BYTES) {
59
+ return {
60
+ ok: false,
61
+ error: `bundle too large: exceeds ${MAX_BUNDLE_TOTAL_BYTES} bytes total`
62
+ };
63
+ }
64
+ out.push({
65
+ path: f.path,
66
+ content_b64: Buffer.from(f.bytes).toString("base64")
67
+ });
68
+ }
69
+ return { ok: true, files: out };
70
+ }
71
+ function rowId(data) {
72
+ if (typeof data !== "object" || data === null) return void 0;
73
+ const r = data;
74
+ const id = r.id ?? r.resource_id ?? r.resourceId;
75
+ return typeof id === "string" ? id : void 0;
76
+ }
77
+ function rowUrl(data) {
78
+ if (typeof data !== "object" || data === null) return void 0;
79
+ const r = data;
80
+ const url = r.url ?? r.origin;
81
+ return typeof url === "string" ? url : void 0;
82
+ }
83
+ async function resolveExistingApp(exec, app, workspace) {
84
+ const got = await exec("browser-app get", { app });
85
+ if (got.ok) {
86
+ const id = rowId(got.data);
87
+ if (id) return { appId: id, url: rowUrl(got.data) };
88
+ }
89
+ const listed = await exec(
90
+ "browser-app list",
91
+ workspace ? { workspace_id: workspace } : {}
92
+ );
93
+ if (listed.ok && Array.isArray(listed.data)) {
94
+ const match = listed.data.find((row) => {
95
+ const r = row;
96
+ return r?.name === app || rowId(r) === app;
97
+ });
98
+ const id = match ? rowId(match) : void 0;
99
+ if (id) return { appId: id, url: rowUrl(match) };
100
+ }
101
+ return null;
102
+ }
103
+ async function deployBundle(opts, files, exec) {
104
+ const built = buildDeployPayload(files);
105
+ if (!built.ok) return { ok: false, error: built.error };
106
+ let appId;
107
+ let url;
108
+ let created = false;
109
+ if (opts.app) {
110
+ const existing = await resolveExistingApp(exec, opts.app, opts.workspace);
111
+ if (existing) {
112
+ appId = existing.appId;
113
+ url = existing.url;
114
+ }
115
+ }
116
+ if (!appId) {
117
+ const createArgs = {
118
+ name: opts.name ?? opts.app ?? defaultAppName(opts.dir)
119
+ };
120
+ if (opts.workspace) createArgs.workspace_id = opts.workspace;
121
+ const createdRes = await exec("browser-app create", createArgs);
122
+ if (!createdRes.ok) {
123
+ return {
124
+ ok: false,
125
+ error: createdRes.error ?? "failed to create app"
126
+ };
127
+ }
128
+ appId = rowId(createdRes.data);
129
+ url = rowUrl(createdRes.data) ?? url;
130
+ created = true;
131
+ if (!appId) {
132
+ return { ok: false, error: "create succeeded but returned no app id" };
133
+ }
134
+ }
135
+ const deployed = await exec("browser-app deploy", {
136
+ app: appId,
137
+ files: built.files
138
+ });
139
+ if (!deployed.ok) {
140
+ return {
141
+ ok: false,
142
+ appId,
143
+ url,
144
+ created,
145
+ error: deployed.error ?? "deploy failed"
146
+ };
147
+ }
148
+ const data = deployed.data;
149
+ const written = typeof data?.written === "number" ? data.written : built.files.length;
150
+ const deploymentId = typeof data?.id === "string" ? data.id : void 0;
151
+ return { ok: true, appId, deploymentId, url, created, written };
152
+ }
153
+ function defaultAppName(dir) {
154
+ const norm = resolve(dir).split(sep).filter(Boolean);
155
+ const base = norm.at(-1);
156
+ if ((base === "dist" || base === "build") && norm.length >= 2) {
157
+ return norm.at(-2) ?? "My App";
158
+ }
159
+ return base ?? "My App";
160
+ }
161
+ function collectDir(dir) {
162
+ const root = resolve(dir);
163
+ const out = [];
164
+ const walk = (abs) => {
165
+ for (const entry of readdirSync(abs, { withFileTypes: true })) {
166
+ const child = join(abs, entry.name);
167
+ if (entry.isDirectory()) {
168
+ walk(child);
169
+ } else if (entry.isFile()) {
170
+ const rel = relative(root, child).split(sep).join("/");
171
+ out.push({ path: rel, bytes: readFileSync(child) });
172
+ }
173
+ }
174
+ };
175
+ walk(root);
176
+ return out;
177
+ }
178
+ async function runAppDeploy(ctx, args, exec, io = { collect: collectDir }) {
179
+ const opts = parseDeployArgs(args);
180
+ if (!opts.dir) {
181
+ ctx.out(
182
+ "idapt app deploy: missing <dir> \u2014 pass the built bundle, e.g. `idapt app deploy ./dist`."
183
+ );
184
+ return 1;
185
+ }
186
+ let files;
187
+ try {
188
+ files = io.collect(opts.dir);
189
+ } catch (err) {
190
+ ctx.out(
191
+ `idapt app deploy: cannot read "${opts.dir}": ${err.message}`
192
+ );
193
+ return 1;
194
+ }
195
+ const result = await deployBundle(opts, files, exec);
196
+ if (!result.ok) {
197
+ ctx.out(`idapt app deploy: ${result.error}`);
198
+ return 1;
199
+ }
200
+ ctx.print(
201
+ [
202
+ `Deployed ${result.written} file${result.written === 1 ? "" : "s"} to ${result.appId}${result.created ? " (new app)" : ""} \u2014 now live${result.deploymentId ? ` (deployment ${result.deploymentId})` : ""}.`,
203
+ result.url ? `Open: ${result.url}` : ""
204
+ ].filter(Boolean).join("\n")
205
+ );
206
+ return 0;
207
+ }
208
+
209
+ // ../../shared/browser-app-sdk-version.ts
210
+ var BROWSER_APP_SDK_DEP_RANGE = "^0.1.0";
211
+
212
+ // src/app/templates.ts
213
+ function normalize(opts) {
214
+ return {
215
+ name: (opts.name || "My App").trim() || "My App",
216
+ icon: (opts.icon || "\u{1F4E6}").trim() || "\u{1F4E6}",
217
+ description: opts.description?.trim() || void 0
218
+ };
219
+ }
220
+ function idaptManifest(n) {
221
+ const manifest = {
222
+ entrypoint: "dist/index.html",
223
+ name: n.name,
224
+ icon: n.icon,
225
+ version: "0.1.0",
226
+ permissions: []
227
+ };
228
+ if (n.description) manifest.description = n.description;
229
+ return manifest;
230
+ }
231
+ var VITEST_CONFIG = `import { defineConfig } from "vitest/config";
232
+
233
+ // Unit tests run in Node \u2014 the mock Idapt SDK needs no DOM. Add
234
+ // \`environment: "jsdom"\` + @testing-library if you render components in tests.
235
+ export default defineConfig({
236
+ test: {
237
+ include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
238
+ },
239
+ });
240
+ `;
241
+ var EXAMPLE_UNIT_TEST = `/**
242
+ * Example unit test \u2014 runs WITHOUT a live Idapt origin via the mock harness.
243
+ *
244
+ * \`createMockIdapt()\` returns a fake Idapt client whose \`data\` is an in-memory
245
+ * KV and whose \`app\` reads from a fixture map, so app logic built on
246
+ * \`client.data\` / \`client.app\` is unit-testable offline (the real \`connect()\`
247
+ * throws when run off an Idapt app subdomain).
248
+ */
249
+ import { createMockIdapt } from "@idapt/browser-app-sdk/testing";
250
+ import { describe, expect, it } from "vitest";
251
+
252
+ describe("idapt mock harness", () => {
253
+ it("round-trips per-user state through the in-memory data KV", async () => {
254
+ const idapt = createMockIdapt();
255
+ await idapt.data.setJSON("prefs.json", { theme: "dark" });
256
+ expect(await idapt.data.getJSON("prefs.json")).toEqual({ theme: "dark" });
257
+ expect(await idapt.data.get("missing.json")).toBeNull();
258
+ });
259
+
260
+ it("reads the app's own bundle from the fixture map", async () => {
261
+ const idapt = createMockIdapt({
262
+ files: { "idapt.json": JSON.stringify({ name: "Demo" }) },
263
+ });
264
+ const names = (await idapt.app.list()).map((f) => f.name);
265
+ expect(names).toContain("idapt.json");
266
+ });
267
+ });
268
+ `;
269
+ var PLAYWRIGHT_CONFIG = `import { defineConfig } from "@playwright/test";
270
+
271
+ // Point PREVIEW_URL at a deployment's preview (or a local \`vite preview\`) and run
272
+ // \`npm run test:e2e\`. The specs assert against the accessibility tree, so they
273
+ // read the app exactly as assistive tech and \`browser-app snapshot\` do.
274
+ export default defineConfig({
275
+ testDir: "./e2e",
276
+ use: { baseURL: process.env.PREVIEW_URL ?? "http://localhost:4173" },
277
+ });
278
+ `;
279
+ var E2E_SPEC = `import { expect, test } from "@playwright/test";
280
+
281
+ // Accessibility-first assertions: querying by ROLE (not a brittle CSS selector)
282
+ // is robust AND proves the app exposes a semantic tree \u2014 the same tree the agent
283
+ // sees via \`browser-app snapshot\`.
284
+ test("renders an accessible top-level heading", async ({ page }) => {
285
+ await page.goto("/");
286
+ await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
287
+ });
288
+ `;
289
+ var IDAPT_CI_YML = `version: 1
290
+ jobs:
291
+ test:
292
+ executor: cloud
293
+ steps:
294
+ - npm install
295
+ - npm test
296
+ build:
297
+ executor: cloud
298
+ needs: [test]
299
+ steps:
300
+ - npm install
301
+ - npm run build
302
+ output:
303
+ paths:
304
+ - dist/**
305
+ deploy:
306
+ target: browser-app
307
+ app: __APP_NAME__
308
+ artifacts: build
309
+ `;
310
+ var ESLINT_A11Y_CONFIG = `import jsxA11y from "eslint-plugin-jsx-a11y";
311
+
312
+ // Accessibility-first: the SAME semantic tree powers assistive tech, the e2e
313
+ // specs, and the agent's \`browser-app snapshot\`. jsx-a11y flags missing labels,
314
+ // alt text, and invalid roles so inaccessible markup fails lint, not review.
315
+ export default [
316
+ {
317
+ files: ["src/**/*.{ts,tsx}"],
318
+ plugins: { "jsx-a11y": jsxA11y },
319
+ rules: jsxA11y.flatConfigs.recommended.rules,
320
+ },
321
+ ];
322
+ `;
323
+ var REACT_INDEX_HTML = `<!doctype html>
324
+ <html lang="en">
325
+ <head>
326
+ <meta charset="utf-8" />
327
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
328
+ <title>__APP_NAME__</title>
329
+ </head>
330
+ <body>
331
+ <div id="root"></div>
332
+ <!-- npm-only: the app bundles @idapt/browser-app-sdk itself (a dependency).
333
+ The SDK auto-connects via the app-key cookie; no script tag needed. -->
334
+ <script type="module" src="/src/main.tsx"></script>
335
+ </body>
336
+ </html>
337
+ `;
338
+ var REACT_PKG = "react";
339
+ var REACT_DOM_CLIENT_PKG = "react-dom/client";
340
+ var REACT_MAIN_TSX = `import { StrictMode } from "${REACT_PKG}";
341
+ import { createRoot } from "${REACT_DOM_CLIENT_PKG}";
342
+ import { App } from "./App";
343
+
344
+ createRoot(document.getElementById("root")!).render(
345
+ <StrictMode>
346
+ <App />
347
+ </StrictMode>,
348
+ );
349
+ `;
350
+ var REACT_APP_TSX = `import { connect } from "@idapt/browser-app-sdk";
351
+ import { useEffect, useState } from "${REACT_PKG}";
352
+
353
+ export function App() {
354
+ const [status, setStatus] = useState("Connecting\u2026");
355
+
356
+ useEffect(() => {
357
+ // On an idapt app subdomain the app-key cookie auto-connects \u2014 no args.
358
+ connect()
359
+ .then(() => setStatus("Connected."))
360
+ .catch(() => setStatus("Could not connect to idapt."));
361
+ }, []);
362
+
363
+ return (
364
+ <main style={{ maxWidth: "36rem", margin: "0 auto", padding: "2rem 1.25rem" }}>
365
+ <h1>__APP_NAME__</h1>
366
+ <p>{status}</p>
367
+ </main>
368
+ );
369
+ }
370
+ `;
371
+ var REACT_VITE_CONFIG = `import react from "@vitejs/plugin-react";
372
+ import { defineConfig } from "vite";
373
+
374
+ // Build to dist/ (the served bundle). Relative base so assets resolve on the
375
+ // app subdomain regardless of mount path.
376
+ export default defineConfig({
377
+ base: "./",
378
+ plugins: [react()],
379
+ build: { outDir: "dist" },
380
+ });
381
+ `;
382
+ var REACT_TSCONFIG = `{
383
+ "compilerOptions": {
384
+ "target": "ES2020",
385
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
386
+ "module": "ESNext",
387
+ "moduleResolution": "Bundler",
388
+ "jsx": "react-jsx",
389
+ "strict": true,
390
+ "skipLibCheck": true,
391
+ "noEmit": true
392
+ },
393
+ "include": ["src"]
394
+ }
395
+ `;
396
+ function getReactScaffold(opts) {
397
+ const n = normalize(opts);
398
+ const pkg = {
399
+ name: "idapt-react-app",
400
+ private: true,
401
+ version: "0.1.0",
402
+ type: "module",
403
+ scripts: {
404
+ build: "vite build",
405
+ dev: "vite",
406
+ test: "vitest run",
407
+ "test:e2e": "playwright test",
408
+ lint: "eslint ."
409
+ },
410
+ dependencies: {
411
+ "@idapt/browser-app-sdk": BROWSER_APP_SDK_DEP_RANGE,
412
+ react: "^18.3.1",
413
+ "react-dom": "^18.3.1"
414
+ },
415
+ devDependencies: {
416
+ "@playwright/test": "^1.48.0",
417
+ "@vitejs/plugin-react": "^4.3.1",
418
+ eslint: "^9.13.0",
419
+ "eslint-plugin-jsx-a11y": "^6.10.0",
420
+ typescript: "^5.5.4",
421
+ vite: "^5.4.0",
422
+ vitest: "^2.1.0"
423
+ },
424
+ idapt: idaptManifest(n)
425
+ };
426
+ return [
427
+ { path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
428
+ ` },
429
+ {
430
+ path: "index.html",
431
+ content: REACT_INDEX_HTML.replaceAll("__APP_NAME__", n.name)
432
+ },
433
+ { path: "vite.config.ts", content: REACT_VITE_CONFIG },
434
+ { path: "vitest.config.ts", content: VITEST_CONFIG },
435
+ { path: "playwright.config.ts", content: PLAYWRIGHT_CONFIG },
436
+ { path: "eslint.config.js", content: ESLINT_A11Y_CONFIG },
437
+ {
438
+ path: "idapt-ci.yml",
439
+ content: IDAPT_CI_YML.replaceAll("__APP_NAME__", n.name)
440
+ },
441
+ { path: "tsconfig.json", content: REACT_TSCONFIG },
442
+ { path: "src/main.tsx", content: REACT_MAIN_TSX },
443
+ {
444
+ path: "src/App.tsx",
445
+ content: REACT_APP_TSX.replaceAll("__APP_NAME__", n.name)
446
+ },
447
+ { path: "src/example.test.ts", content: EXAMPLE_UNIT_TEST },
448
+ { path: "e2e/app.spec.ts", content: E2E_SPEC }
449
+ ];
450
+ }
451
+ var VANILLA_INDEX_HTML = `<!doctype html>
452
+ <html lang="en">
453
+ <head>
454
+ <meta charset="utf-8" />
455
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
456
+ <title>__APP_NAME__</title>
457
+ <link rel="stylesheet" href="main.css" />
458
+ </head>
459
+ <body>
460
+ <main class="app">
461
+ <h1>__APP_ICON__ __APP_NAME__</h1>
462
+ <p id="status">Connecting\u2026</p>
463
+ </main>
464
+ <!-- npm-only: the app bundles @idapt/browser-app-sdk itself (a dependency). -->
465
+ <script type="module" src="main.js"></script>
466
+ </body>
467
+ </html>
468
+ `;
469
+ var VANILLA_MAIN_TS = `// __APP_NAME__ \u2014 a buildable idapt browser-app (esbuild).
470
+ import { connect } from "@idapt/browser-app-sdk";
471
+
472
+ const statusEl = document.getElementById("status");
473
+ function setStatus(text: string) {
474
+ if (statusEl) statusEl.textContent = text;
475
+ }
476
+
477
+ // On an idapt app subdomain the app-key cookie auto-connects \u2014 no args.
478
+ connect()
479
+ .then(() => setStatus("Connected."))
480
+ .catch(() => setStatus("Could not connect to idapt."));
481
+
482
+ export {};
483
+ `;
484
+ var VANILLA_MAIN_CSS = `body {
485
+ margin: 0;
486
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
487
+ background: #0b0b0f;
488
+ color: #f4f4f5;
489
+ }
490
+ .app {
491
+ max-width: 36rem;
492
+ margin: 0 auto;
493
+ padding: 2rem 1.25rem;
494
+ }
495
+ `;
496
+ var VANILLA_BUILD_SH = `set -e
497
+ mkdir -p dist
498
+ node_modules/.bin/esbuild src/main.ts --bundle --format=esm --outfile=dist/main.js
499
+ cp index.html dist/index.html
500
+ cp src/main.css dist/main.css
501
+ `;
502
+ function getVanillaScaffold(opts) {
503
+ const n = normalize(opts);
504
+ const pkg = {
505
+ name: "idapt-vanilla-app",
506
+ private: true,
507
+ version: "0.1.0",
508
+ type: "module",
509
+ scripts: {
510
+ // The build step runs build.sh so it can bundle + copy static assets.
511
+ build: "sh build.sh",
512
+ test: "vitest run",
513
+ "test:e2e": "playwright test"
514
+ },
515
+ dependencies: {
516
+ "@idapt/browser-app-sdk": BROWSER_APP_SDK_DEP_RANGE
517
+ },
518
+ devDependencies: {
519
+ "@playwright/test": "^1.48.0",
520
+ esbuild: "^0.23.0",
521
+ typescript: "^5.5.4",
522
+ vitest: "^2.1.0"
523
+ },
524
+ idapt: idaptManifest(n)
525
+ };
526
+ return [
527
+ { path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
528
+ ` },
529
+ { path: "build.sh", content: VANILLA_BUILD_SH },
530
+ { path: "vitest.config.ts", content: VITEST_CONFIG },
531
+ { path: "playwright.config.ts", content: PLAYWRIGHT_CONFIG },
532
+ {
533
+ path: "idapt-ci.yml",
534
+ content: IDAPT_CI_YML.replaceAll("__APP_NAME__", n.name)
535
+ },
536
+ {
537
+ path: "index.html",
538
+ content: VANILLA_INDEX_HTML.replaceAll("__APP_NAME__", n.name).replaceAll(
539
+ "__APP_ICON__",
540
+ n.icon
541
+ )
542
+ },
543
+ {
544
+ path: "src/main.ts",
545
+ content: VANILLA_MAIN_TS.replaceAll("__APP_NAME__", n.name)
546
+ },
547
+ { path: "src/main.css", content: VANILLA_MAIN_CSS },
548
+ { path: "src/example.test.ts", content: EXAMPLE_UNIT_TEST },
549
+ { path: "e2e/app.spec.ts", content: E2E_SPEC }
550
+ ];
551
+ }
552
+ function getScaffoldFiles(template, opts) {
553
+ return template === "react" ? getReactScaffold(opts) : getVanillaScaffold(opts);
554
+ }
555
+
556
+ // src/app/init.ts
557
+ function parseInitArgs(args) {
558
+ let name;
559
+ let template = "react";
560
+ let dir = ".";
561
+ let force = false;
562
+ for (let i = 0; i < args.length; i++) {
563
+ const a = args[i];
564
+ if (a === "--template" || a === "-t") {
565
+ template = normTemplate(args[++i]);
566
+ } else if (a.startsWith("--template=")) {
567
+ template = normTemplate(a.slice("--template=".length));
568
+ } else if (a === "--dir" || a === "-d") {
569
+ dir = args[++i] ?? ".";
570
+ } else if (a.startsWith("--dir=")) {
571
+ dir = a.slice("--dir=".length) || ".";
572
+ } else if (a === "--force" || a === "-f") {
573
+ force = true;
574
+ } else if (!a.startsWith("-") && name === void 0) {
575
+ name = a;
576
+ }
577
+ }
578
+ return { name, template, dir, force };
579
+ }
580
+ function normTemplate(v) {
581
+ return v === "vanilla" ? "vanilla" : "react";
582
+ }
583
+ function planInit(opts, io) {
584
+ if (opts.template !== "react" && opts.template !== "vanilla") {
585
+ return {
586
+ ok: false,
587
+ written: [],
588
+ error: `unknown template "${opts.template}" (use react or vanilla)`
589
+ };
590
+ }
591
+ if (!opts.force && io.dirIsNonEmpty(opts.dir)) {
592
+ return {
593
+ ok: false,
594
+ written: [],
595
+ error: `target "${opts.dir}" is not empty \u2014 pass --force to scaffold into it anyway`
596
+ };
597
+ }
598
+ const files = getScaffoldFiles(opts.template, {
599
+ name: opts.name ?? "My App"
600
+ });
601
+ const written = [];
602
+ for (const f of files) {
603
+ io.writeFile(join(opts.dir, f.path), f.content);
604
+ written.push(f.path);
605
+ }
606
+ return { ok: true, written };
607
+ }
608
+ function runAppInit(ctx, args) {
609
+ const opts = parseInitArgs(args);
610
+ const io = {
611
+ dirIsNonEmpty: (p) => {
612
+ const abs = resolve(p);
613
+ if (!existsSync(abs)) return false;
614
+ try {
615
+ return readdirSync(abs).length > 0;
616
+ } catch {
617
+ return false;
618
+ }
619
+ },
620
+ writeFile: (p, content) => {
621
+ const abs = resolve(p);
622
+ mkdirSync(dirname(abs), { recursive: true });
623
+ writeFileSync(abs, content, "utf-8");
624
+ }
625
+ };
626
+ const result = planInit(opts, io);
627
+ if (!result.ok) {
628
+ ctx.out(`idapt app init: ${result.error}`);
629
+ return 1;
630
+ }
631
+ ctx.print(
632
+ `Scaffolded a ${opts.template} browser-app in ${opts.dir} (${result.written.length} files).`
633
+ );
634
+ ctx.out(
635
+ [
636
+ "",
637
+ "Next steps:",
638
+ ` cd ${opts.dir}`,
639
+ " npm install && npm run build",
640
+ " idapt app deploy ./dist"
641
+ ].join("\n")
642
+ );
643
+ return 0;
644
+ }
645
+
646
+ // src/app/index.ts
647
+ var APP_USAGE = `idapt app \u2014 local browser-app developer loop
648
+
649
+ Usage:
650
+ idapt app init [name] [--template react|vanilla] [--dir .] [--force]
651
+ idapt app deploy <dir> [--app <id|name>] [--workspace <id>] [--name <name>]
652
+
653
+ init Scaffold an npm browser-app project locally (then npm install && npm run build)
654
+ deploy Upload a locally-built dist/ to a browser-app (creates one if --app is absent/unknown)`;
655
+ async function runApp(ctx, args) {
656
+ const sub = args[0];
657
+ const rest = args.slice(1);
658
+ if (sub === "init") {
659
+ return runAppInit(ctx, rest);
660
+ }
661
+ if (sub === "deploy") {
662
+ if (!ctx.token) {
663
+ ctx.out(
664
+ "idapt app deploy: not signed in. Run `idapt login` (or set IDAPT_API_KEY)."
665
+ );
666
+ return 1;
667
+ }
668
+ const transport = createFetchTransport({
669
+ baseUrl: ctx.baseUrl,
670
+ token: ctx.token,
671
+ userAgent: USER_AGENT
672
+ });
673
+ const exec = (commandName, cmdArgs) => executeCommand(commandName, cmdArgs, { transport, mode: "json" });
674
+ return runAppDeploy(ctx, rest, exec);
675
+ }
676
+ if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
677
+ ctx.print(APP_USAGE);
678
+ return sub ? 0 : 1;
679
+ }
680
+ ctx.out(`idapt app: unknown subcommand "${sub}".
681
+
682
+ ${APP_USAGE}`);
683
+ return 1;
684
+ }
13
685
  function canOpenBrowser(env, isTty) {
14
686
  if (!isTty) return false;
15
687
  if (env.SSH_CONNECTION || env.SSH_TTY) return false;
@@ -157,7 +829,7 @@ function loginAuthCode(io) {
157
829
  const base = trimRight(io.baseUrl);
158
830
  const { verifier, challenge } = generatePkce();
159
831
  const state = randomState();
160
- return new Promise((resolve, reject) => {
832
+ return new Promise((resolve3, reject) => {
161
833
  let settled = false;
162
834
  const finish = (fn) => {
163
835
  if (settled) return;
@@ -216,7 +888,7 @@ function loginAuthCode(io) {
216
888
  },
217
889
  io.userAgent
218
890
  ).then(
219
- (tok) => finish(() => resolve(tok)),
891
+ (tok) => finish(() => resolve3(tok)),
220
892
  (e) => finish(() => reject(e))
221
893
  );
222
894
  });
@@ -256,8 +928,8 @@ If it doesn't open, visit:
256
928
  });
257
929
  });
258
930
  }
259
- var sleep = (ms, signal) => new Promise((resolve, reject) => {
260
- const t = setTimeout(resolve, ms);
931
+ var sleep = (ms, signal) => new Promise((resolve3, reject) => {
932
+ const t = setTimeout(resolve3, ms);
261
933
  signal?.addEventListener("abort", () => {
262
934
  clearTimeout(t);
263
935
  reject(new AuthError("sign-in cancelled"));
@@ -516,6 +1188,10 @@ async function runStatus(ctx) {
516
1188
  }
517
1189
 
518
1190
  // src/doc.ts
1191
+ function dumpJson(resource) {
1192
+ const entries = resource ? commandCatalog.filter((c) => c.resource === resource) : commandCatalog;
1193
+ return JSON.stringify(entries, null, 2);
1194
+ }
519
1195
  function helpIndex() {
520
1196
  const resources = listResources().sort().join(", ");
521
1197
  return [
@@ -525,6 +1201,7 @@ function helpIndex() {
525
1201
  " idapt <resource> <verb> [args] Run a command (e.g. idapt drive list)",
526
1202
  " idapt help [resource [verb]] Show a command's contract",
527
1203
  " idapt instructions [resource] Show a resource's playbook",
1204
+ " idapt app init | deploy Scaffold + ship a local browser-app",
528
1205
  " idapt login | logout | whoami Authenticate",
529
1206
  " idapt upgrade Update to the latest version",
530
1207
  "",
@@ -552,9 +1229,11 @@ function resourceHelp(resource, cmds) {
552
1229
  ].join("\n");
553
1230
  }
554
1231
  function renderHelpDoc(tokens) {
555
- if (tokens.length === 0) return helpIndex();
556
- if (tokens.length === 1) {
557
- const resource = tokens[0];
1232
+ const positional = tokens.filter((t) => !t.startsWith("-"));
1233
+ if (tokens.includes("--dump-json")) return dumpJson(positional[0]);
1234
+ if (positional.length === 0) return helpIndex();
1235
+ if (positional.length === 1) {
1236
+ const resource = positional[0];
558
1237
  const cmds2 = commandsForResource(resource);
559
1238
  if (cmds2.length === 0) {
560
1239
  return `Unknown resource "${resource}".
@@ -563,14 +1242,14 @@ ${helpIndex()}`;
563
1242
  }
564
1243
  return resourceHelp(resource, cmds2);
565
1244
  }
566
- const command = `${tokens[0]} ${tokens[1]}`;
1245
+ const command = `${positional[0]} ${positional[1]}`;
567
1246
  const spec = findCommand(command);
568
1247
  if (spec) return renderHelp(spec);
569
- const cmds = commandsForResource(tokens[0]);
1248
+ const cmds = commandsForResource(positional[0]);
570
1249
  if (cmds.length > 0) {
571
1250
  return `Unknown command "${command}".
572
1251
 
573
- ${resourceHelp(tokens[0], cmds)}`;
1252
+ ${resourceHelp(positional[0], cmds)}`;
574
1253
  }
575
1254
  return `Unknown command "${command}".
576
1255
 
@@ -703,12 +1382,12 @@ function detectManager(scriptPath, env, channel) {
703
1382
  };
704
1383
  }
705
1384
  function spawnInherit(cmd, args) {
706
- return new Promise((resolve) => {
1385
+ return new Promise((resolve3) => {
707
1386
  const child = process.platform === "win32" ? spawn(process.env.ComSpec || "cmd.exe", ["/c", cmd, ...args], {
708
1387
  stdio: "inherit"
709
1388
  }) : spawn(cmd, args, { stdio: "inherit" });
710
- child.on("error", () => resolve(127));
711
- child.on("close", (code) => resolve(code ?? 0));
1389
+ child.on("error", () => resolve3(127));
1390
+ child.on("close", (code) => resolve3(code ?? 0));
712
1391
  });
713
1392
  }
714
1393
  async function runUpgrade(io, opts) {
@@ -798,6 +1477,31 @@ function extractValueFlag(argv, name) {
798
1477
  function buildCommandFromArgv(argv) {
799
1478
  return ["idapt", ...argv.map(quoteToken)].join(" ");
800
1479
  }
1480
+ function resolveJsonArgSource(argv, io) {
1481
+ const resolveVal = (val) => {
1482
+ if (val === "-") return io.readStdin();
1483
+ if (val.startsWith("@")) return io.readFile(val.slice(1));
1484
+ return null;
1485
+ };
1486
+ const out = [];
1487
+ for (let i = 0; i < argv.length; i++) {
1488
+ const tok = argv[i];
1489
+ if (tok.startsWith("--json=")) {
1490
+ const resolved = resolveVal(tok.slice("--json=".length));
1491
+ out.push(resolved === null ? tok : `--json=${resolved}`);
1492
+ continue;
1493
+ }
1494
+ if (tok === "--json" && i + 1 < argv.length) {
1495
+ out.push(tok);
1496
+ const resolved = resolveVal(argv[i + 1]);
1497
+ out.push(resolved === null ? argv[i + 1] : resolved);
1498
+ i++;
1499
+ continue;
1500
+ }
1501
+ out.push(tok);
1502
+ }
1503
+ return out;
1504
+ }
801
1505
  function isDocCommand(rest) {
802
1506
  if (rest[0] === "help" || rest[0] === "instructions") return true;
803
1507
  return rest.some(
@@ -865,6 +1569,23 @@ async function run(io) {
865
1569
  );
866
1570
  return 1;
867
1571
  }
1572
+ if (cmd === "app") {
1573
+ const appCred = await resolveCredential({
1574
+ apiKeyFlag,
1575
+ env: io.env,
1576
+ baseUrl,
1577
+ userAgent: USER_AGENT
1578
+ }).catch(() => null);
1579
+ return runApp(
1580
+ {
1581
+ out: io.stderr,
1582
+ print: io.stdout,
1583
+ baseUrl,
1584
+ token: appCred?.token
1585
+ },
1586
+ rest.slice(1)
1587
+ );
1588
+ }
868
1589
  const doc = isDocCommand(rest);
869
1590
  let token;
870
1591
  const resolved = await resolveCredential({
@@ -887,7 +1608,11 @@ async function run(io) {
887
1608
  token,
888
1609
  userAgent: USER_AGENT
889
1610
  });
890
- const result = await execute(buildCommandFromArgv(rest), {
1611
+ const restWithJson = resolveJsonArgSource(rest, {
1612
+ readFile: (p) => readFileSync(p, "utf-8"),
1613
+ readStdin: () => readFileSync(0, "utf-8")
1614
+ });
1615
+ const result = await execute(buildCommandFromArgv(restWithJson), {
891
1616
  transport,
892
1617
  mode: autoMode(io.isTty, requested)
893
1618
  });
@@ -923,6 +1648,6 @@ if (typeof process !== "undefined" && isCliEntrypoint(process.argv[1])) {
923
1648
  void main();
924
1649
  }
925
1650
 
926
- export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, run };
1651
+ export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, resolveJsonArgSource, run };
927
1652
  //# sourceMappingURL=bin.js.map
928
1653
  //# sourceMappingURL=bin.js.map