@idapt/cli 1.9.0 → 1.10.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,684 @@
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-CX7FTE47.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.10.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
+ // src/app/templates.ts
210
+ function normalize(opts) {
211
+ return {
212
+ name: (opts.name || "My App").trim() || "My App",
213
+ icon: (opts.icon || "\u{1F4E6}").trim() || "\u{1F4E6}",
214
+ description: opts.description?.trim() || void 0
215
+ };
216
+ }
217
+ function idaptManifest(n) {
218
+ const manifest = {
219
+ entrypoint: "dist/index.html",
220
+ name: n.name,
221
+ icon: n.icon,
222
+ version: "0.1.0",
223
+ permissions: []
224
+ };
225
+ if (n.description) manifest.description = n.description;
226
+ return manifest;
227
+ }
228
+ var VITEST_CONFIG = `import { defineConfig } from "vitest/config";
229
+
230
+ // Unit tests run in Node \u2014 the mock Idapt SDK needs no DOM. Add
231
+ // \`environment: "jsdom"\` + @testing-library if you render components in tests.
232
+ export default defineConfig({
233
+ test: {
234
+ include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
235
+ },
236
+ });
237
+ `;
238
+ var EXAMPLE_UNIT_TEST = `/**
239
+ * Example unit test \u2014 runs WITHOUT a live Idapt origin via the mock harness.
240
+ *
241
+ * \`createMockIdapt()\` returns a fake Idapt client whose \`data\` is an in-memory
242
+ * KV and whose \`app\` reads from a fixture map, so app logic built on
243
+ * \`client.data\` / \`client.app\` is unit-testable offline (the real \`connect()\`
244
+ * throws when run off an Idapt app subdomain).
245
+ */
246
+ import { createMockIdapt } from "@idapt/browser-app-sdk/testing";
247
+ import { describe, expect, it } from "vitest";
248
+
249
+ describe("idapt mock harness", () => {
250
+ it("round-trips per-user state through the in-memory data KV", async () => {
251
+ const idapt = createMockIdapt();
252
+ await idapt.data.setJSON("prefs.json", { theme: "dark" });
253
+ expect(await idapt.data.getJSON("prefs.json")).toEqual({ theme: "dark" });
254
+ expect(await idapt.data.get("missing.json")).toBeNull();
255
+ });
256
+
257
+ it("reads the app's own bundle from the fixture map", async () => {
258
+ const idapt = createMockIdapt({
259
+ files: { "idapt.json": JSON.stringify({ name: "Demo" }) },
260
+ });
261
+ const names = (await idapt.app.list()).map((f) => f.name);
262
+ expect(names).toContain("idapt.json");
263
+ });
264
+ });
265
+ `;
266
+ var PLAYWRIGHT_CONFIG = `import { defineConfig } from "@playwright/test";
267
+
268
+ // Point PREVIEW_URL at a deployment's preview (or a local \`vite preview\`) and run
269
+ // \`npm run test:e2e\`. The specs assert against the accessibility tree, so they
270
+ // read the app exactly as assistive tech and \`browser-app snapshot\` do.
271
+ export default defineConfig({
272
+ testDir: "./e2e",
273
+ use: { baseURL: process.env.PREVIEW_URL ?? "http://localhost:4173" },
274
+ });
275
+ `;
276
+ var E2E_SPEC = `import { expect, test } from "@playwright/test";
277
+
278
+ // Accessibility-first assertions: querying by ROLE (not a brittle CSS selector)
279
+ // is robust AND proves the app exposes a semantic tree \u2014 the same tree the agent
280
+ // sees via \`browser-app snapshot\`.
281
+ test("renders an accessible top-level heading", async ({ page }) => {
282
+ await page.goto("/");
283
+ await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
284
+ });
285
+ `;
286
+ var IDAPT_CI_YML = `version: 1
287
+ jobs:
288
+ test:
289
+ executor: cloud
290
+ steps:
291
+ - npm install
292
+ - npm test
293
+ build:
294
+ executor: cloud
295
+ needs: [test]
296
+ steps:
297
+ - npm install
298
+ - npm run build
299
+ output:
300
+ paths:
301
+ - dist/**
302
+ deploy:
303
+ target: browser-app
304
+ app: __APP_NAME__
305
+ artifacts: build
306
+ `;
307
+ var ESLINT_A11Y_CONFIG = `import jsxA11y from "eslint-plugin-jsx-a11y";
308
+
309
+ // Accessibility-first: the SAME semantic tree powers assistive tech, the e2e
310
+ // specs, and the agent's \`browser-app snapshot\`. jsx-a11y flags missing labels,
311
+ // alt text, and invalid roles so inaccessible markup fails lint, not review.
312
+ export default [
313
+ {
314
+ files: ["src/**/*.{ts,tsx}"],
315
+ plugins: { "jsx-a11y": jsxA11y },
316
+ rules: jsxA11y.flatConfigs.recommended.rules,
317
+ },
318
+ ];
319
+ `;
320
+ var REACT_INDEX_HTML = `<!doctype html>
321
+ <html lang="en">
322
+ <head>
323
+ <meta charset="utf-8" />
324
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
325
+ <title>__APP_NAME__</title>
326
+ </head>
327
+ <body>
328
+ <div id="root"></div>
329
+ <!-- npm-only: the app bundles @idapt/browser-app-sdk itself (a dependency).
330
+ The SDK auto-connects via the app-key cookie; no script tag needed. -->
331
+ <script type="module" src="/src/main.tsx"></script>
332
+ </body>
333
+ </html>
334
+ `;
335
+ var REACT_PKG = "react";
336
+ var REACT_DOM_CLIENT_PKG = "react-dom/client";
337
+ var REACT_MAIN_TSX = `import { StrictMode } from "${REACT_PKG}";
338
+ import { createRoot } from "${REACT_DOM_CLIENT_PKG}";
339
+ import { App } from "./App";
340
+
341
+ createRoot(document.getElementById("root")!).render(
342
+ <StrictMode>
343
+ <App />
344
+ </StrictMode>,
345
+ );
346
+ `;
347
+ var REACT_APP_TSX = `import { connect } from "@idapt/browser-app-sdk";
348
+ import { useEffect, useState } from "${REACT_PKG}";
349
+
350
+ export function App() {
351
+ const [status, setStatus] = useState("Connecting\u2026");
352
+
353
+ useEffect(() => {
354
+ // On an idapt app subdomain the app-key cookie auto-connects \u2014 no args.
355
+ connect()
356
+ .then(() => setStatus("Connected."))
357
+ .catch(() => setStatus("Could not connect to idapt."));
358
+ }, []);
359
+
360
+ return (
361
+ <main style={{ maxWidth: "36rem", margin: "0 auto", padding: "2rem 1.25rem" }}>
362
+ <h1>__APP_NAME__</h1>
363
+ <p>{status}</p>
364
+ </main>
365
+ );
366
+ }
367
+ `;
368
+ var REACT_VITE_CONFIG = `import react from "@vitejs/plugin-react";
369
+ import { defineConfig } from "vite";
370
+
371
+ // Build to dist/ (the served bundle). Relative base so assets resolve on the
372
+ // app subdomain regardless of mount path.
373
+ export default defineConfig({
374
+ base: "./",
375
+ plugins: [react()],
376
+ build: { outDir: "dist" },
377
+ });
378
+ `;
379
+ var REACT_TSCONFIG = `{
380
+ "compilerOptions": {
381
+ "target": "ES2020",
382
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
383
+ "module": "ESNext",
384
+ "moduleResolution": "Bundler",
385
+ "jsx": "react-jsx",
386
+ "strict": true,
387
+ "skipLibCheck": true,
388
+ "noEmit": true
389
+ },
390
+ "include": ["src"]
391
+ }
392
+ `;
393
+ function getReactScaffold(opts) {
394
+ const n = normalize(opts);
395
+ const pkg = {
396
+ name: "idapt-react-app",
397
+ private: true,
398
+ version: "0.1.0",
399
+ type: "module",
400
+ scripts: {
401
+ build: "vite build",
402
+ dev: "vite",
403
+ test: "vitest run",
404
+ "test:e2e": "playwright test",
405
+ lint: "eslint ."
406
+ },
407
+ dependencies: {
408
+ "@idapt/browser-app-sdk": "^0.1.0",
409
+ react: "^18.3.1",
410
+ "react-dom": "^18.3.1"
411
+ },
412
+ devDependencies: {
413
+ "@playwright/test": "^1.48.0",
414
+ "@vitejs/plugin-react": "^4.3.1",
415
+ eslint: "^9.13.0",
416
+ "eslint-plugin-jsx-a11y": "^6.10.0",
417
+ typescript: "^5.5.4",
418
+ vite: "^5.4.0",
419
+ vitest: "^2.1.0"
420
+ },
421
+ idapt: idaptManifest(n)
422
+ };
423
+ return [
424
+ { path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
425
+ ` },
426
+ {
427
+ path: "index.html",
428
+ content: REACT_INDEX_HTML.replaceAll("__APP_NAME__", n.name)
429
+ },
430
+ { path: "vite.config.ts", content: REACT_VITE_CONFIG },
431
+ { path: "vitest.config.ts", content: VITEST_CONFIG },
432
+ { path: "playwright.config.ts", content: PLAYWRIGHT_CONFIG },
433
+ { path: "eslint.config.js", content: ESLINT_A11Y_CONFIG },
434
+ {
435
+ path: "idapt-ci.yml",
436
+ content: IDAPT_CI_YML.replaceAll("__APP_NAME__", n.name)
437
+ },
438
+ { path: "tsconfig.json", content: REACT_TSCONFIG },
439
+ { path: "src/main.tsx", content: REACT_MAIN_TSX },
440
+ {
441
+ path: "src/App.tsx",
442
+ content: REACT_APP_TSX.replaceAll("__APP_NAME__", n.name)
443
+ },
444
+ { path: "src/example.test.ts", content: EXAMPLE_UNIT_TEST },
445
+ { path: "e2e/app.spec.ts", content: E2E_SPEC }
446
+ ];
447
+ }
448
+ var VANILLA_INDEX_HTML = `<!doctype html>
449
+ <html lang="en">
450
+ <head>
451
+ <meta charset="utf-8" />
452
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
453
+ <title>__APP_NAME__</title>
454
+ <link rel="stylesheet" href="main.css" />
455
+ </head>
456
+ <body>
457
+ <main class="app">
458
+ <h1>__APP_ICON__ __APP_NAME__</h1>
459
+ <p id="status">Connecting\u2026</p>
460
+ </main>
461
+ <!-- npm-only: the app bundles @idapt/browser-app-sdk itself (a dependency). -->
462
+ <script type="module" src="main.js"></script>
463
+ </body>
464
+ </html>
465
+ `;
466
+ var VANILLA_MAIN_TS = `// __APP_NAME__ \u2014 a buildable idapt browser-app (esbuild).
467
+ import { connect } from "@idapt/browser-app-sdk";
468
+
469
+ const statusEl = document.getElementById("status");
470
+ function setStatus(text: string) {
471
+ if (statusEl) statusEl.textContent = text;
472
+ }
473
+
474
+ // On an idapt app subdomain the app-key cookie auto-connects \u2014 no args.
475
+ connect()
476
+ .then(() => setStatus("Connected."))
477
+ .catch(() => setStatus("Could not connect to idapt."));
478
+
479
+ export {};
480
+ `;
481
+ var VANILLA_MAIN_CSS = `body {
482
+ margin: 0;
483
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
484
+ background: #0b0b0f;
485
+ color: #f4f4f5;
486
+ }
487
+ .app {
488
+ max-width: 36rem;
489
+ margin: 0 auto;
490
+ padding: 2rem 1.25rem;
491
+ }
492
+ `;
493
+ var VANILLA_BUILD_SH = `set -e
494
+ mkdir -p dist
495
+ node_modules/.bin/esbuild src/main.ts --bundle --format=esm --outfile=dist/main.js
496
+ cp index.html dist/index.html
497
+ cp src/main.css dist/main.css
498
+ `;
499
+ function getVanillaScaffold(opts) {
500
+ const n = normalize(opts);
501
+ const pkg = {
502
+ name: "idapt-vanilla-app",
503
+ private: true,
504
+ version: "0.1.0",
505
+ type: "module",
506
+ scripts: {
507
+ // The build step runs build.sh so it can bundle + copy static assets.
508
+ build: "sh build.sh",
509
+ test: "vitest run",
510
+ "test:e2e": "playwright test"
511
+ },
512
+ dependencies: {
513
+ "@idapt/browser-app-sdk": "^0.1.0"
514
+ },
515
+ devDependencies: {
516
+ "@playwright/test": "^1.48.0",
517
+ esbuild: "^0.23.0",
518
+ typescript: "^5.5.4",
519
+ vitest: "^2.1.0"
520
+ },
521
+ idapt: idaptManifest(n)
522
+ };
523
+ return [
524
+ { path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
525
+ ` },
526
+ { path: "build.sh", content: VANILLA_BUILD_SH },
527
+ { path: "vitest.config.ts", content: VITEST_CONFIG },
528
+ { path: "playwright.config.ts", content: PLAYWRIGHT_CONFIG },
529
+ {
530
+ path: "idapt-ci.yml",
531
+ content: IDAPT_CI_YML.replaceAll("__APP_NAME__", n.name)
532
+ },
533
+ {
534
+ path: "index.html",
535
+ content: VANILLA_INDEX_HTML.replaceAll("__APP_NAME__", n.name).replaceAll(
536
+ "__APP_ICON__",
537
+ n.icon
538
+ )
539
+ },
540
+ {
541
+ path: "src/main.ts",
542
+ content: VANILLA_MAIN_TS.replaceAll("__APP_NAME__", n.name)
543
+ },
544
+ { path: "src/main.css", content: VANILLA_MAIN_CSS },
545
+ { path: "src/example.test.ts", content: EXAMPLE_UNIT_TEST },
546
+ { path: "e2e/app.spec.ts", content: E2E_SPEC }
547
+ ];
548
+ }
549
+ function getScaffoldFiles(template, opts) {
550
+ return template === "react" ? getReactScaffold(opts) : getVanillaScaffold(opts);
551
+ }
552
+
553
+ // src/app/init.ts
554
+ function parseInitArgs(args) {
555
+ let name;
556
+ let template = "react";
557
+ let dir = ".";
558
+ let force = false;
559
+ for (let i = 0; i < args.length; i++) {
560
+ const a = args[i];
561
+ if (a === "--template" || a === "-t") {
562
+ template = normTemplate(args[++i]);
563
+ } else if (a.startsWith("--template=")) {
564
+ template = normTemplate(a.slice("--template=".length));
565
+ } else if (a === "--dir" || a === "-d") {
566
+ dir = args[++i] ?? ".";
567
+ } else if (a.startsWith("--dir=")) {
568
+ dir = a.slice("--dir=".length) || ".";
569
+ } else if (a === "--force" || a === "-f") {
570
+ force = true;
571
+ } else if (!a.startsWith("-") && name === void 0) {
572
+ name = a;
573
+ }
574
+ }
575
+ return { name, template, dir, force };
576
+ }
577
+ function normTemplate(v) {
578
+ return v === "vanilla" ? "vanilla" : "react";
579
+ }
580
+ function planInit(opts, io) {
581
+ if (opts.template !== "react" && opts.template !== "vanilla") {
582
+ return {
583
+ ok: false,
584
+ written: [],
585
+ error: `unknown template "${opts.template}" (use react or vanilla)`
586
+ };
587
+ }
588
+ if (!opts.force && io.dirIsNonEmpty(opts.dir)) {
589
+ return {
590
+ ok: false,
591
+ written: [],
592
+ error: `target "${opts.dir}" is not empty \u2014 pass --force to scaffold into it anyway`
593
+ };
594
+ }
595
+ const files = getScaffoldFiles(opts.template, {
596
+ name: opts.name ?? "My App"
597
+ });
598
+ const written = [];
599
+ for (const f of files) {
600
+ io.writeFile(join(opts.dir, f.path), f.content);
601
+ written.push(f.path);
602
+ }
603
+ return { ok: true, written };
604
+ }
605
+ function runAppInit(ctx, args) {
606
+ const opts = parseInitArgs(args);
607
+ const io = {
608
+ dirIsNonEmpty: (p) => {
609
+ const abs = resolve(p);
610
+ if (!existsSync(abs)) return false;
611
+ try {
612
+ return readdirSync(abs).length > 0;
613
+ } catch {
614
+ return false;
615
+ }
616
+ },
617
+ writeFile: (p, content) => {
618
+ const abs = resolve(p);
619
+ mkdirSync(dirname(abs), { recursive: true });
620
+ writeFileSync(abs, content, "utf-8");
621
+ }
622
+ };
623
+ const result = planInit(opts, io);
624
+ if (!result.ok) {
625
+ ctx.out(`idapt app init: ${result.error}`);
626
+ return 1;
627
+ }
628
+ ctx.print(
629
+ `Scaffolded a ${opts.template} browser-app in ${opts.dir} (${result.written.length} files).`
630
+ );
631
+ ctx.out(
632
+ [
633
+ "",
634
+ "Next steps:",
635
+ ` cd ${opts.dir}`,
636
+ " npm install && npm run build",
637
+ " idapt app deploy ./dist"
638
+ ].join("\n")
639
+ );
640
+ return 0;
641
+ }
642
+
643
+ // src/app/index.ts
644
+ var APP_USAGE = `idapt app \u2014 local browser-app developer loop
645
+
646
+ Usage:
647
+ idapt app init [name] [--template react|vanilla] [--dir .] [--force]
648
+ idapt app deploy <dir> [--app <id|name>] [--workspace <id>] [--name <name>]
649
+
650
+ init Scaffold an npm browser-app project locally (then npm install && npm run build)
651
+ deploy Upload a locally-built dist/ to a browser-app (creates one if --app is absent/unknown)`;
652
+ async function runApp(ctx, args) {
653
+ const sub = args[0];
654
+ const rest = args.slice(1);
655
+ if (sub === "init") {
656
+ return runAppInit(ctx, rest);
657
+ }
658
+ if (sub === "deploy") {
659
+ if (!ctx.token) {
660
+ ctx.out(
661
+ "idapt app deploy: not signed in. Run `idapt login` (or set IDAPT_API_KEY)."
662
+ );
663
+ return 1;
664
+ }
665
+ const transport = createFetchTransport({
666
+ baseUrl: ctx.baseUrl,
667
+ token: ctx.token,
668
+ userAgent: USER_AGENT
669
+ });
670
+ const exec = (commandName, cmdArgs) => executeCommand(commandName, cmdArgs, { transport, mode: "json" });
671
+ return runAppDeploy(ctx, rest, exec);
672
+ }
673
+ if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
674
+ ctx.print(APP_USAGE);
675
+ return sub ? 0 : 1;
676
+ }
677
+ ctx.out(`idapt app: unknown subcommand "${sub}".
678
+
679
+ ${APP_USAGE}`);
680
+ return 1;
681
+ }
13
682
  function canOpenBrowser(env, isTty) {
14
683
  if (!isTty) return false;
15
684
  if (env.SSH_CONNECTION || env.SSH_TTY) return false;
@@ -157,7 +826,7 @@ function loginAuthCode(io) {
157
826
  const base = trimRight(io.baseUrl);
158
827
  const { verifier, challenge } = generatePkce();
159
828
  const state = randomState();
160
- return new Promise((resolve, reject) => {
829
+ return new Promise((resolve3, reject) => {
161
830
  let settled = false;
162
831
  const finish = (fn) => {
163
832
  if (settled) return;
@@ -216,7 +885,7 @@ function loginAuthCode(io) {
216
885
  },
217
886
  io.userAgent
218
887
  ).then(
219
- (tok) => finish(() => resolve(tok)),
888
+ (tok) => finish(() => resolve3(tok)),
220
889
  (e) => finish(() => reject(e))
221
890
  );
222
891
  });
@@ -256,8 +925,8 @@ If it doesn't open, visit:
256
925
  });
257
926
  });
258
927
  }
259
- var sleep = (ms, signal) => new Promise((resolve, reject) => {
260
- const t = setTimeout(resolve, ms);
928
+ var sleep = (ms, signal) => new Promise((resolve3, reject) => {
929
+ const t = setTimeout(resolve3, ms);
261
930
  signal?.addEventListener("abort", () => {
262
931
  clearTimeout(t);
263
932
  reject(new AuthError("sign-in cancelled"));
@@ -516,6 +1185,10 @@ async function runStatus(ctx) {
516
1185
  }
517
1186
 
518
1187
  // src/doc.ts
1188
+ function dumpJson(resource) {
1189
+ const entries = resource ? commandCatalog.filter((c) => c.resource === resource) : commandCatalog;
1190
+ return JSON.stringify(entries, null, 2);
1191
+ }
519
1192
  function helpIndex() {
520
1193
  const resources = listResources().sort().join(", ");
521
1194
  return [
@@ -525,6 +1198,7 @@ function helpIndex() {
525
1198
  " idapt <resource> <verb> [args] Run a command (e.g. idapt drive list)",
526
1199
  " idapt help [resource [verb]] Show a command's contract",
527
1200
  " idapt instructions [resource] Show a resource's playbook",
1201
+ " idapt app init | deploy Scaffold + ship a local browser-app",
528
1202
  " idapt login | logout | whoami Authenticate",
529
1203
  " idapt upgrade Update to the latest version",
530
1204
  "",
@@ -552,9 +1226,11 @@ function resourceHelp(resource, cmds) {
552
1226
  ].join("\n");
553
1227
  }
554
1228
  function renderHelpDoc(tokens) {
555
- if (tokens.length === 0) return helpIndex();
556
- if (tokens.length === 1) {
557
- const resource = tokens[0];
1229
+ const positional = tokens.filter((t) => !t.startsWith("-"));
1230
+ if (tokens.includes("--dump-json")) return dumpJson(positional[0]);
1231
+ if (positional.length === 0) return helpIndex();
1232
+ if (positional.length === 1) {
1233
+ const resource = positional[0];
558
1234
  const cmds2 = commandsForResource(resource);
559
1235
  if (cmds2.length === 0) {
560
1236
  return `Unknown resource "${resource}".
@@ -563,14 +1239,14 @@ ${helpIndex()}`;
563
1239
  }
564
1240
  return resourceHelp(resource, cmds2);
565
1241
  }
566
- const command = `${tokens[0]} ${tokens[1]}`;
1242
+ const command = `${positional[0]} ${positional[1]}`;
567
1243
  const spec = findCommand(command);
568
1244
  if (spec) return renderHelp(spec);
569
- const cmds = commandsForResource(tokens[0]);
1245
+ const cmds = commandsForResource(positional[0]);
570
1246
  if (cmds.length > 0) {
571
1247
  return `Unknown command "${command}".
572
1248
 
573
- ${resourceHelp(tokens[0], cmds)}`;
1249
+ ${resourceHelp(positional[0], cmds)}`;
574
1250
  }
575
1251
  return `Unknown command "${command}".
576
1252
 
@@ -703,12 +1379,12 @@ function detectManager(scriptPath, env, channel) {
703
1379
  };
704
1380
  }
705
1381
  function spawnInherit(cmd, args) {
706
- return new Promise((resolve) => {
1382
+ return new Promise((resolve3) => {
707
1383
  const child = process.platform === "win32" ? spawn(process.env.ComSpec || "cmd.exe", ["/c", cmd, ...args], {
708
1384
  stdio: "inherit"
709
1385
  }) : spawn(cmd, args, { stdio: "inherit" });
710
- child.on("error", () => resolve(127));
711
- child.on("close", (code) => resolve(code ?? 0));
1386
+ child.on("error", () => resolve3(127));
1387
+ child.on("close", (code) => resolve3(code ?? 0));
712
1388
  });
713
1389
  }
714
1390
  async function runUpgrade(io, opts) {
@@ -798,6 +1474,31 @@ function extractValueFlag(argv, name) {
798
1474
  function buildCommandFromArgv(argv) {
799
1475
  return ["idapt", ...argv.map(quoteToken)].join(" ");
800
1476
  }
1477
+ function resolveJsonArgSource(argv, io) {
1478
+ const resolveVal = (val) => {
1479
+ if (val === "-") return io.readStdin();
1480
+ if (val.startsWith("@")) return io.readFile(val.slice(1));
1481
+ return null;
1482
+ };
1483
+ const out = [];
1484
+ for (let i = 0; i < argv.length; i++) {
1485
+ const tok = argv[i];
1486
+ if (tok.startsWith("--json=")) {
1487
+ const resolved = resolveVal(tok.slice("--json=".length));
1488
+ out.push(resolved === null ? tok : `--json=${resolved}`);
1489
+ continue;
1490
+ }
1491
+ if (tok === "--json" && i + 1 < argv.length) {
1492
+ out.push(tok);
1493
+ const resolved = resolveVal(argv[i + 1]);
1494
+ out.push(resolved === null ? argv[i + 1] : resolved);
1495
+ i++;
1496
+ continue;
1497
+ }
1498
+ out.push(tok);
1499
+ }
1500
+ return out;
1501
+ }
801
1502
  function isDocCommand(rest) {
802
1503
  if (rest[0] === "help" || rest[0] === "instructions") return true;
803
1504
  return rest.some(
@@ -865,6 +1566,23 @@ async function run(io) {
865
1566
  );
866
1567
  return 1;
867
1568
  }
1569
+ if (cmd === "app") {
1570
+ const appCred = await resolveCredential({
1571
+ apiKeyFlag,
1572
+ env: io.env,
1573
+ baseUrl,
1574
+ userAgent: USER_AGENT
1575
+ }).catch(() => null);
1576
+ return runApp(
1577
+ {
1578
+ out: io.stderr,
1579
+ print: io.stdout,
1580
+ baseUrl,
1581
+ token: appCred?.token
1582
+ },
1583
+ rest.slice(1)
1584
+ );
1585
+ }
868
1586
  const doc = isDocCommand(rest);
869
1587
  let token;
870
1588
  const resolved = await resolveCredential({
@@ -887,7 +1605,11 @@ async function run(io) {
887
1605
  token,
888
1606
  userAgent: USER_AGENT
889
1607
  });
890
- const result = await execute(buildCommandFromArgv(rest), {
1608
+ const restWithJson = resolveJsonArgSource(rest, {
1609
+ readFile: (p) => readFileSync(p, "utf-8"),
1610
+ readStdin: () => readFileSync(0, "utf-8")
1611
+ });
1612
+ const result = await execute(buildCommandFromArgv(restWithJson), {
891
1613
  transport,
892
1614
  mode: autoMode(io.isTty, requested)
893
1615
  });
@@ -923,6 +1645,6 @@ if (typeof process !== "undefined" && isCliEntrypoint(process.argv[1])) {
923
1645
  void main();
924
1646
  }
925
1647
 
926
- export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, run };
1648
+ export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, resolveJsonArgSource, run };
927
1649
  //# sourceMappingURL=bin.js.map
928
1650
  //# sourceMappingURL=bin.js.map