@storystick/cli 0.0.0-stage → 0.2.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CONTENT-IMPORT.md +349 -0
  2. package/LICENSE +112 -0
  3. package/PUBLIC-API.md +30 -0
  4. package/README.md +263 -2
  5. package/assets/next-v1/app/[[...pathname]]/error.tsx +10 -0
  6. package/assets/next-v1/app/[[...pathname]]/not-found.tsx +8 -0
  7. package/assets/next-v1/app/[[...pathname]]/page.tsx +38 -0
  8. package/assets/next-v1/app/api/storystick/[...action]/route.ts +4 -0
  9. package/assets/next-v1/app/layout.tsx +28 -0
  10. package/assets/next-v1/app/sitemap.ts +3 -0
  11. package/assets/next-v1/env.local.example +13 -0
  12. package/assets/next-v1/globals.css +17 -0
  13. package/assets/next-v1/lib/site.ts +10 -0
  14. package/assets/next-v1/next.config.ts +4 -0
  15. package/assets/skills/storystick-development/SKILL.md +79 -0
  16. package/assets/skills/storystick-development/references/setup.md +152 -0
  17. package/assets/skills/storystick-development/references/story-and-next.md +49 -0
  18. package/dist/_backfill-recommendation.d.ts +12 -0
  19. package/dist/_backfill-recommendation.js +18 -0
  20. package/dist/asset-import.d.ts +50 -0
  21. package/dist/asset-import.js +160 -0
  22. package/dist/assistant-read-client.d.ts +3 -0
  23. package/dist/assistant-read-client.js +8 -0
  24. package/dist/auth-device.d.ts +153 -0
  25. package/dist/auth-device.js +390 -0
  26. package/dist/bootstrap-client.d.ts +52 -0
  27. package/dist/bootstrap-client.js +19 -0
  28. package/dist/cli-error.d.ts +54 -0
  29. package/dist/cli-error.js +107 -0
  30. package/dist/cli.d.ts +68 -0
  31. package/dist/cli.js +1520 -0
  32. package/dist/commands/archive.d.ts +17 -0
  33. package/dist/commands/archive.js +86 -0
  34. package/dist/commands/backfill-refs.d.ts +54 -0
  35. package/dist/commands/backfill-refs.js +217 -0
  36. package/dist/commands/connect.d.ts +11 -0
  37. package/dist/commands/connect.js +124 -0
  38. package/dist/commands/connection.d.ts +57 -0
  39. package/dist/commands/connection.js +83 -0
  40. package/dist/commands/content.d.ts +11 -0
  41. package/dist/commands/content.js +107 -0
  42. package/dist/commands/delivery.d.ts +22 -0
  43. package/dist/commands/delivery.js +99 -0
  44. package/dist/commands/info.d.ts +35 -0
  45. package/dist/commands/info.js +62 -0
  46. package/dist/commands/list.d.ts +28 -0
  47. package/dist/commands/list.js +116 -0
  48. package/dist/commands/migrate.d.ts +80 -0
  49. package/dist/commands/migrate.js +550 -0
  50. package/dist/commands/release.d.ts +8 -0
  51. package/dist/commands/release.js +60 -0
  52. package/dist/commands/setup.d.ts +6 -0
  53. package/dist/commands/setup.js +58 -0
  54. package/dist/commands/slice-previews.d.ts +17 -0
  55. package/dist/commands/slice-previews.js +233 -0
  56. package/dist/commands/source.d.ts +82 -0
  57. package/dist/commands/source.js +192 -0
  58. package/dist/content-import.d.ts +78 -0
  59. package/dist/content-import.js +210 -0
  60. package/dist/generated-transaction.d.ts +22 -0
  61. package/dist/generated-transaction.js +330 -0
  62. package/dist/hub-client.d.ts +72 -0
  63. package/dist/hub-client.js +178 -0
  64. package/dist/init-operation.d.ts +98 -0
  65. package/dist/init-operation.js +427 -0
  66. package/dist/init-readiness.d.ts +10 -0
  67. package/dist/init-readiness.js +50 -0
  68. package/dist/init-scaffold.d.ts +54 -0
  69. package/dist/init-scaffold.js +439 -0
  70. package/dist/init.d.ts +51 -0
  71. package/dist/init.js +789 -0
  72. package/dist/mcp-execution.d.ts +15 -0
  73. package/dist/mcp-execution.js +90 -0
  74. package/dist/mcp.d.ts +7 -0
  75. package/dist/mcp.js +41 -0
  76. package/dist/migration-artifact.d.ts +13 -0
  77. package/dist/migration-artifact.js +154 -0
  78. package/dist/operations-client.d.ts +137 -0
  79. package/dist/operations-client.js +130 -0
  80. package/dist/output.d.ts +347 -0
  81. package/dist/output.js +771 -0
  82. package/dist/project-access.d.ts +18 -0
  83. package/dist/project-access.js +1 -0
  84. package/dist/project-connection.d.ts +62 -0
  85. package/dist/project-connection.js +347 -0
  86. package/dist/project-execution-client.d.ts +2 -0
  87. package/dist/project-execution-client.js +33 -0
  88. package/dist/push-client.d.ts +100 -0
  89. package/dist/push-client.js +160 -0
  90. package/dist/release-context.d.ts +13 -0
  91. package/dist/release-context.js +39 -0
  92. package/dist/release-schema-client.d.ts +16 -0
  93. package/dist/release-schema-client.js +33 -0
  94. package/dist/release-sync.d.ts +36 -0
  95. package/dist/release-sync.js +31 -0
  96. package/dist/resolve.d.ts +15 -0
  97. package/dist/resolve.js +80 -0
  98. package/dist/runtime-config.d.ts +18 -0
  99. package/dist/runtime-config.js +86 -0
  100. package/dist/schema-export.d.ts +10 -0
  101. package/dist/schema-export.js +28 -0
  102. package/dist/setup.d.ts +65 -0
  103. package/dist/setup.js +135 -0
  104. package/dist/source-git.d.ts +36 -0
  105. package/dist/source-git.js +197 -0
  106. package/dist/starter-resolver.d.ts +82 -0
  107. package/dist/starter-resolver.js +495 -0
  108. package/dist/sync-transaction.d.ts +29 -0
  109. package/dist/sync-transaction.js +159 -0
  110. package/dist/transport.d.ts +8 -0
  111. package/dist/transport.js +73 -0
  112. package/examples/sanity/README.md +79 -0
  113. package/examples/sanity/convert.mjs +228 -0
  114. package/examples/sanity/fixture/data.ndjson +7 -0
  115. package/examples/sanity/fixture/mapping.json +44 -0
  116. package/examples/sanity/fixture/originals/mark.svg +1 -0
  117. package/examples/sanity/stories/articleBody.story +23 -0
  118. package/examples/sanity/stories/copy.story +7 -0
  119. package/examples/sanity/stories/page.story +19 -0
  120. package/examples/sanity/stories/picture.story +7 -0
  121. package/examples/sanity/stories/settings.story +9 -0
  122. package/examples/sanity/stories/stack.story +10 -0
  123. package/package.json +75 -3
@@ -0,0 +1,107 @@
1
+ import { CONTENT_IMPORT_CONFLICT, DELIVERY_UNAVAILABLE } from '@storystick/types/errors/codes';
2
+ import { resolve } from 'node:path';
3
+ import { readContentImport, prepareContentImportAssets, importRunIdentity, prepareContentImport, applyContentImport, verifyContentImport, contentImportTransport } from '../content-import.js';
4
+ import { requireProjectConnection } from '../project-connection.js';
5
+ import { resolveCliRuntimeConfig } from '../runtime-config.js';
6
+ import { StorystickError } from '../cli-error.js';
7
+ import { configureOutput, emitJsonResult, exitJsonFailure, exitWithError, getOutputMode, logMessage, logOutput, withInterrupt } from '../output.js';
8
+ import { readAssetImport, prepareAssetImport, applyAssetImport, assetImportTransport, uploadBudgetTransport, readUploadBudget } from '../asset-import.js';
9
+ export async function runContent(action, target, options) {
10
+ configureOutput({ json: options.json === true });
11
+ const { signal, dispose } = withInterrupt();
12
+ const command = `content ${action}`;
13
+ try {
14
+ const root = resolve(options.root ?? '.'), project = options.project ?? requireProjectConnection(root).shortId;
15
+ if (!/^[0-9a-hjkmnp-tv-z]{8}$/.test(project))
16
+ throw new Error('Expected a Storystick project short ID.');
17
+ const runtime = resolveCliRuntimeConfig({ rootDir: root });
18
+ const transport = contentImportTransport(project, { signal, rootDir: root });
19
+ let result;
20
+ if (options.renew && (action !== 'import-asset' || !options.apply))
21
+ throw new Error('--renew <upload-session-id> requires content import-asset --apply.');
22
+ if (action === 'upload-budget' || action === 'upload-budget-cancel') {
23
+ if (options.resume || options.newRun)
24
+ throw new Error('Document import flags do not apply to upload budgets.');
25
+ const budget = uploadBudgetTransport(project, { signal, rootDir: root });
26
+ if (action === 'upload-budget-cancel' || !target) {
27
+ if (options.apply || options.dryRun || options.plan || (action === 'upload-budget-cancel' && !target))
28
+ throw new Error('Use content upload-budget for status, or content upload-budget-cancel <budget-id>.');
29
+ result = await budget(action === 'upload-budget-cancel' ? { operation: 'cancel', requestId: target } : { operation: 'status' });
30
+ }
31
+ else {
32
+ if (Boolean(options.dryRun) === Boolean(options.apply) || (options.apply && !options.plan))
33
+ throw new Error('Use content upload-budget <budget.json> with --dry-run or --apply --plan <digest>.');
34
+ const requested = await readUploadBudget(resolve(root, target));
35
+ result = await budget({ operation: options.apply ? 'grant' : 'plan', ...requested, ...(options.apply ? { planDigest: options.plan } : {}) });
36
+ }
37
+ }
38
+ else if (action === 'import-asset') {
39
+ if (!target || Boolean(options.dryRun) === Boolean(options.apply) || options.resume || options.newRun)
40
+ throw new Error('Use content import-asset <manifest.json> with --dry-run or --apply --plan <digest>.');
41
+ if (options.apply && !options.plan)
42
+ throw new Error('Apply requires --plan <digest> from a previous dry run.');
43
+ const source = await readAssetImport(resolve(root, target));
44
+ const assets = assetImportTransport(project, { signal, rootDir: root });
45
+ result = options.apply ? await applyAssetImport(assets, source.file, source.input, options.plan, { signal, renewSessionId: options.renew }) : await prepareAssetImport(assets, source.input);
46
+ }
47
+ else if (action === 'import') {
48
+ if (!target || Boolean(options.dryRun) === Boolean(options.apply))
49
+ throw new Error('Use content import <manifest.json> with exactly one of --dry-run or --apply --plan <digest>.');
50
+ if (options.apply && !options.plan)
51
+ throw new Error('Apply requires --plan <digest> from a previous dry run.');
52
+ if (options.newRun && (!options.dryRun || options.resume))
53
+ throw new Error('--new-run requires --dry-run and cannot be combined with --resume.');
54
+ const local = await readContentImport(resolve(root, target));
55
+ const assets = assetImportTransport(project, { signal, rootDir: root });
56
+ const source = await prepareContentImportAssets(assets, local);
57
+ const runId = await importRunIdentity({ root, project, gateway: runtime.gatewayUrl, manifest: source.manifest, resume: options.resume, newRun: options.newRun });
58
+ logMessage(`Import ${runId}`);
59
+ const prepared = await prepareContentImport(transport, { runId, ...source });
60
+ if (options.apply) {
61
+ if (prepared.planDigest !== options.plan)
62
+ throw new Error('The supplied digest does not match the sealed server plan.');
63
+ if (!['planned', 'applying', 'complete'].includes(prepared.state))
64
+ throw new Error(`Import cannot apply while ${prepared.state}.`);
65
+ for (const asset of source.assets) {
66
+ const current = await transport({ operation: 'status', runId });
67
+ if (!['planned', 'applying', 'complete'].includes(current.state))
68
+ throw new Error(`Import cannot apply while ${current.state}.`);
69
+ await applyAssetImport(assets, asset.file, asset.input, asset.plan.planDigest, { signal });
70
+ }
71
+ result = await applyContentImport(transport, runId, options.plan);
72
+ }
73
+ else
74
+ result = { ...prepared,
75
+ assetBytes: source.assets.reduce((sum, asset) => sum + asset.input.declaredSizeBytes, 0),
76
+ assets: source.assets.map(({ input, plan }) => ({ key: input.key, bytes: input.declaredSizeBytes, ...plan })),
77
+ };
78
+ }
79
+ else {
80
+ if (!target || !['import-status', 'import-cancel', 'verify'].includes(action))
81
+ throw new Error('Use content import-status, content import-cancel or content verify <run-id>.');
82
+ if (options.apply || options.dryRun || options.plan || options.resume || options.newRun)
83
+ throw new Error('Import execution flags are only valid for content import.');
84
+ result = action === 'verify' ? await verifyContentImport(transport, target)
85
+ : await transport({ operation: action === 'import-status' ? 'status' : 'cancel', runId: target });
86
+ if (action === 'verify') {
87
+ const proof = result;
88
+ if (!proof.verified)
89
+ throw new StorystickError('Import readback does not match its receipts.', { ...CONTENT_IMPORT_CONFLICT, status: 409, payload: proof });
90
+ if (!proof.projected)
91
+ throw new StorystickError('Content is committed; Content Plane projection is pending.', { ...DELIVERY_UNAVAILABLE, status: 503, payload: proof });
92
+ }
93
+ }
94
+ if (options.json)
95
+ emitJsonResult({ command, ok: true, data: result });
96
+ else
97
+ logOutput(JSON.stringify(result, null, 2));
98
+ }
99
+ catch (err) {
100
+ if (getOutputMode() === 'json')
101
+ exitJsonFailure({ command, err });
102
+ exitWithError(err);
103
+ }
104
+ finally {
105
+ dispose();
106
+ }
107
+ }
@@ -0,0 +1,22 @@
1
+ export type DeliveryOptions = {
2
+ root?: string;
3
+ project?: string;
4
+ fromVercel?: boolean;
5
+ team?: string;
6
+ destination?: string;
7
+ siteUrl?: string;
8
+ rootDirectory?: string;
9
+ buildCommand?: string;
10
+ dryRun?: boolean;
11
+ json?: boolean;
12
+ };
13
+ export declare function deliveryProposal(root: string, options: DeliveryOptions): {
14
+ teamId: any;
15
+ destinationId: any;
16
+ publicUrl: string;
17
+ projectSettings: {
18
+ rootDirectory: string | null;
19
+ buildCommand: string | null;
20
+ };
21
+ };
22
+ export declare function runDelivery(operation: string, options: DeliveryOptions): Promise<void>;
@@ -0,0 +1,99 @@
1
+ import { resolve } from "node:path";
2
+ import { callHubApi } from "../hub-client.js";
3
+ import { resolveCliRuntimeConfig } from "../runtime-config.js";
4
+ import { resolveProjectConnection } from "../project-connection.js";
5
+ import { inspectSetup } from "../setup.js";
6
+ import { StorystickError } from "../cli-error.js";
7
+ import { configureOutput, emitJsonResult, exitJsonFailure, exitWithError, getOutputMode, logMessage, withInterrupt, } from "../output.js";
8
+ export function deliveryProposal(root, options) {
9
+ if (options.fromVercel && (options.team || options.destination))
10
+ throw Error("Choose --from-vercel or explicit --team and --destination.");
11
+ const linked = options.fromVercel ? inspectSetup(root).hosting : null;
12
+ const teamId = linked?.teamId ?? options.team, destinationId = linked?.projectId ?? options.destination;
13
+ if (!teamId ||
14
+ !/^team_[a-zA-Z0-9]+$/.test(teamId) ||
15
+ !destinationId ||
16
+ !/^prj_[a-zA-Z0-9]+$/.test(destinationId))
17
+ throw Error("Specify --team and --destination, or link this folder with Vercel and use --from-vercel.");
18
+ let url;
19
+ try {
20
+ url = new URL(options.siteUrl ?? "");
21
+ }
22
+ catch {
23
+ throw new StorystickError("--site-url must be the HTTPS origin already attached to the Vercel project.", {
24
+ code: "DELIVERY_CONFIGURATION_INVALID",
25
+ category: "validation",
26
+ retryable: false,
27
+ });
28
+ }
29
+ if (url.protocol !== "https:" ||
30
+ url.username ||
31
+ url.password ||
32
+ url.pathname !== "/" ||
33
+ url.search ||
34
+ url.hash)
35
+ throw new StorystickError("--site-url must be the HTTPS origin already attached to the Vercel project.", {
36
+ code: "DELIVERY_CONFIGURATION_INVALID",
37
+ category: "validation",
38
+ retryable: false,
39
+ });
40
+ return {
41
+ teamId,
42
+ destinationId,
43
+ publicUrl: url.origin,
44
+ projectSettings: {
45
+ rootDirectory: options.rootDirectory ?? null,
46
+ buildCommand: options.buildCommand ?? null,
47
+ },
48
+ };
49
+ }
50
+ export async function runDelivery(operation, options) {
51
+ configureOutput({ json: options.json === true });
52
+ const { signal, dispose } = withInterrupt();
53
+ try {
54
+ if (!["status", "configure", "verify"].includes(operation))
55
+ throw Error("Use delivery status, configure or verify.");
56
+ if (options.dryRun && operation !== "configure")
57
+ throw Error("--dry-run applies to delivery configure only.");
58
+ if (process.env.STORYSTICK_RELEASE_SESSION)
59
+ throw Error("Hosting configuration requires an account CLI session outside the sandbox.");
60
+ const rootDir = resolve(options.root ?? ".");
61
+ const configuration = operation === "configure"
62
+ ? deliveryProposal(rootDir, options)
63
+ : undefined;
64
+ const projectShortId = options.project ?? resolveProjectConnection(rootDir)?.shortId;
65
+ if (!projectShortId)
66
+ throw new StorystickError("Connect a project or specify --project <shortId>.", {
67
+ code: "PROJECT_NOT_CONNECTED",
68
+ category: "validation",
69
+ retryable: false,
70
+ });
71
+ const result = options.dryRun
72
+ ? { projectShortId, configuration, saved: false }
73
+ : await callHubApi("/api/cli/project-setup", {
74
+ rootDir,
75
+ gatewayUrl: resolveCliRuntimeConfig({ rootDir }).gatewayUrl,
76
+ method: "POST",
77
+ body: {
78
+ projectShortId,
79
+ operation: `delivery-${operation}`,
80
+ ...(configuration ? { configuration } : {}),
81
+ },
82
+ signal,
83
+ interactiveAuth: false,
84
+ timeoutMs: 90_000,
85
+ });
86
+ if (getOutputMode() === "json")
87
+ emitJsonResult({ command: "delivery", ok: true, data: result });
88
+ else
89
+ logMessage(JSON.stringify(result, null, 2));
90
+ }
91
+ catch (err) {
92
+ if (getOutputMode() === "json")
93
+ exitJsonFailure({ command: "delivery", err });
94
+ exitWithError(err);
95
+ }
96
+ finally {
97
+ dispose();
98
+ }
99
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `storystick info [<projectId>]` (SPEC-1 Item #4).
3
+ *
4
+ * Without an argument: detail of the active project (detected from the nearest
5
+ * complete, validated ProjectConnection). With an argument: detail of the
6
+ * given shortId.
7
+ *
8
+ * Flag `--json` emits the full payload for scripting.
9
+ */
10
+ import type { ProjectAccessSummary } from '../project-access.js';
11
+ export interface ProjectInfo extends ProjectAccessSummary {
12
+ /** 8-char Crockford shortId — the public project identifier. */
13
+ shortId: string;
14
+ /** Kebab-cased URL label derived from name (may be empty). */
15
+ urlLabel: string;
16
+ name: string;
17
+ /**
18
+ * Internal Convex `_id`. Kept in the payload for ops debug workflows
19
+ * (support-ticket correlation, Convex trace lookup) but never rendered
20
+ * in the CLI pretty-print — `--json` only. Distinct from the SDK's
21
+ * `ProjectConfig.projectId`, which is the public shortId.
22
+ */
23
+ internalId: string;
24
+ plan: 'free' | 'pro' | 'team';
25
+ createdAt: number;
26
+ apiKeyCount?: number;
27
+ secretKeyCount?: number;
28
+ publishableKeyCount?: number;
29
+ endpoint: string;
30
+ }
31
+ export interface InfoOptions {
32
+ json?: boolean;
33
+ root?: string;
34
+ }
35
+ export declare function runInfo(projectIdArg: string | undefined, options?: InfoOptions): Promise<void>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `storystick info [<projectId>]` (SPEC-1 Item #4).
3
+ *
4
+ * Without an argument: detail of the active project (detected from the nearest
5
+ * complete, validated ProjectConnection). With an argument: detail of the
6
+ * given shortId.
7
+ *
8
+ * Flag `--json` emits the full payload for scripting.
9
+ */
10
+ import pc from 'picocolors';
11
+ import { resolve } from 'node:path';
12
+ import { callHubApi } from '../hub-client.js';
13
+ import { requireProjectConnection } from '../project-connection.js';
14
+ import { configureOutput, emitJsonResult, exitJsonFailure, exitWithError, getOutputMode, logOutput, promptDeviceAuth, withInterrupt, } from '../output.js';
15
+ export async function runInfo(projectIdArg, options = {}) {
16
+ configureOutput({ json: options.json === true });
17
+ const { signal, dispose } = withInterrupt();
18
+ try {
19
+ let projectId = projectIdArg;
20
+ if (!projectId) {
21
+ projectId = requireProjectConnection(resolve(options.root ?? '.')).shortId;
22
+ }
23
+ const info = await callHubApi(`/api/cli/projects/for-user-by-shortId?shortId=${encodeURIComponent(projectId)}`, { onAuthPrompt: promptDeviceAuth, signal });
24
+ if (options.json) {
25
+ // ADR-072 §D15: BREAKING — payload now wrapped in the standard
26
+ // envelope (was a raw ProjectInfo object pre-ADR-072).
27
+ emitJsonResult({ command: 'info', ok: true, data: info });
28
+ return;
29
+ }
30
+ const created = new Date(info.createdAt).toISOString().slice(0, 10);
31
+ const rows = [
32
+ ['Project:', info.shortId],
33
+ ['Name:', info.name],
34
+ ['URL:', `/p/${info.urlLabel ? `${info.urlLabel}-${info.shortId}` : info.shortId}`],
35
+ ['Endpoint:', info.endpoint],
36
+ ['Plan:', info.plan],
37
+ ['Content role:', info.contentRole],
38
+ ['Access:', info.accessSource],
39
+ ['Team role:', info.teamRole ?? '—'],
40
+ ['Created:', created],
41
+ ];
42
+ if (info.apiKeyCount !== undefined &&
43
+ info.secretKeyCount !== undefined &&
44
+ info.publishableKeyCount !== undefined) {
45
+ const keyBreakdown = info.apiKeyCount === 0
46
+ ? '0'
47
+ : `${info.apiKeyCount} (${info.secretKeyCount} secret, ${info.publishableKeyCount} publishable)`;
48
+ rows.push(['API keys:', keyBreakdown]);
49
+ }
50
+ const body = rows.map(([label, value]) => pc.dim(label.padEnd(12)) + value).join('\n') + '\n';
51
+ logOutput(body);
52
+ }
53
+ catch (err) {
54
+ if (getOutputMode() === 'json') {
55
+ exitJsonFailure({ command: 'info', err });
56
+ }
57
+ exitWithError(err);
58
+ }
59
+ finally {
60
+ dispose();
61
+ }
62
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `storystick list` / `storystick ls` (SPEC-1 Item #4).
3
+ *
4
+ * Lists the authenticated user's projects. Marks the active project from the
5
+ * nearest complete, validated ProjectConnection via bold formatting.
6
+ *
7
+ * Flags:
8
+ * --json Emit JSON to stdout, no ANSI. Active-project detection still
9
+ * applies; the marker surfaces as `active: true` in the payload.
10
+ * --all Include archived (soft-deleted) projects. Adds a `status`
11
+ * column and surfaces `deletedAt`.
12
+ */
13
+ import type { ProjectAccessSummary } from '../project-access.js';
14
+ export interface ListedProject extends ProjectAccessSummary {
15
+ /** 8-char Crockford shortId — the public project identifier. */
16
+ shortId: string;
17
+ /** Kebab-cased URL label derived from name (may be empty). */
18
+ urlLabel: string;
19
+ name: string;
20
+ plan: 'free' | 'pro' | 'team';
21
+ deletedAt: number | null;
22
+ createdAt: number;
23
+ }
24
+ export interface ListOptions {
25
+ json?: boolean;
26
+ all?: boolean;
27
+ }
28
+ export declare function runList(options?: ListOptions): Promise<void>;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * `storystick list` / `storystick ls` (SPEC-1 Item #4).
3
+ *
4
+ * Lists the authenticated user's projects. Marks the active project from the
5
+ * nearest complete, validated ProjectConnection via bold formatting.
6
+ *
7
+ * Flags:
8
+ * --json Emit JSON to stdout, no ANSI. Active-project detection still
9
+ * applies; the marker surfaces as `active: true` in the payload.
10
+ * --all Include archived (soft-deleted) projects. Adds a `status`
11
+ * column and surfaces `deletedAt`.
12
+ */
13
+ import pc from 'picocolors';
14
+ import { callHubApi } from '../hub-client.js';
15
+ import { resolveProjectConnection } from '../project-connection.js';
16
+ import { configureOutput, emitJsonResult, exitJsonFailure, exitWithError, getOutputMode, logMessage, logOutput, promptDeviceAuth, withInterrupt, } from '../output.js';
17
+ // ─── Entry point ──────────────────────────────────────────────
18
+ export async function runList(options = {}) {
19
+ configureOutput({ json: options.json === true });
20
+ const { signal, dispose } = withInterrupt();
21
+ try {
22
+ const path = '/api/cli/projects/list-for-user' +
23
+ (options.all ? '?includeArchived=1' : '');
24
+ const projects = await callHubApi(path, {
25
+ onAuthPrompt: promptDeviceAuth,
26
+ signal,
27
+ });
28
+ const active = resolveProjectConnection()?.shortId ?? null;
29
+ if (options.json) {
30
+ // ADR-072 §D15: BREAKING — payload is wrapped in the standard
31
+ // envelope (was a flat array pre-ADR-072). Migration guide in
32
+ // docs-notes/migration-adr-072.md (Batch 8).
33
+ const payload = projects.map((p) => ({
34
+ shortId: p.shortId,
35
+ urlLabel: p.urlLabel,
36
+ name: p.name,
37
+ teamRole: p.teamRole,
38
+ contentRole: p.contentRole,
39
+ accessSource: p.accessSource,
40
+ capabilities: p.capabilities,
41
+ plan: p.plan,
42
+ status: deriveStatus(p.deletedAt),
43
+ archived: p.deletedAt !== null,
44
+ active: p.shortId === active,
45
+ }));
46
+ emitJsonResult({ command: 'list', ok: true, data: payload });
47
+ return;
48
+ }
49
+ if (projects.length === 0) {
50
+ logMessage('No projects found. Try ' +
51
+ pc.bold('storystick init <name>') +
52
+ ' to create one.');
53
+ return;
54
+ }
55
+ // Pretty-print — build column widths so roles and plans stay aligned.
56
+ const rows = projects.map((p) => ({
57
+ active: p.shortId === active,
58
+ shortId: p.shortId,
59
+ name: p.name,
60
+ teamRole: p.teamRole ?? '—',
61
+ contentRole: p.contentRole,
62
+ accessSource: p.accessSource,
63
+ plan: p.plan,
64
+ status: deriveStatus(p.deletedAt),
65
+ }));
66
+ const idW = Math.max(8, ...rows.map((r) => r.shortId.length));
67
+ const nameW = Math.max(4, ...rows.map((r) => r.name.length));
68
+ const contentRoleW = Math.max(7, ...rows.map((r) => r.contentRole.length));
69
+ const accessW = Math.max(6, ...rows.map((r) => r.accessSource.length));
70
+ const teamRoleW = Math.max(4, ...rows.map((r) => r.teamRole.length));
71
+ const planW = Math.max(4, ...rows.map((r) => r.plan.length));
72
+ const lines = [];
73
+ const header = [
74
+ pc.dim('ID'.padEnd(idW)),
75
+ pc.dim('NAME'.padEnd(nameW)),
76
+ pc.dim('CONTENT'.padEnd(contentRoleW)),
77
+ pc.dim('ACCESS'.padEnd(accessW)),
78
+ pc.dim('TEAM'.padEnd(teamRoleW)),
79
+ pc.dim('PLAN'.padEnd(planW)),
80
+ ];
81
+ if (options.all)
82
+ header.push(pc.dim('STATUS'));
83
+ lines.push(header.join(' '));
84
+ // Active project is distinguished by a bold shortId — the ● marker was
85
+ // dropped per ADR-061 style rule 2 ("two icons total").
86
+ for (const r of rows) {
87
+ const id = r.active ? pc.bold(r.shortId.padEnd(idW)) : r.shortId.padEnd(idW);
88
+ const line = [
89
+ id,
90
+ r.name.padEnd(nameW),
91
+ r.contentRole.padEnd(contentRoleW),
92
+ r.accessSource.padEnd(accessW),
93
+ r.teamRole.padEnd(teamRoleW),
94
+ r.plan.padEnd(planW),
95
+ ];
96
+ if (options.all) {
97
+ line.push(r.status === 'archived' ? pc.yellow(r.status) : r.status);
98
+ }
99
+ lines.push(line.join(' '));
100
+ }
101
+ logOutput(lines.join('\n') + '\n');
102
+ }
103
+ catch (err) {
104
+ if (getOutputMode() === 'json') {
105
+ exitJsonFailure({ command: 'list', err });
106
+ }
107
+ exitWithError(err);
108
+ }
109
+ finally {
110
+ dispose();
111
+ }
112
+ }
113
+ // ─── Helpers ──────────────────────────────────────────────────
114
+ function deriveStatus(deletedAt) {
115
+ return deletedAt == null ? 'active' : 'archived';
116
+ }
@@ -0,0 +1,80 @@
1
+ import type { SchemaChangeRecipeV1, SchemaChangeRunViewV1, SerializedSchema } from '@storystick/types';
2
+ import { type SchemaChangeEvidenceBody } from '../operations-client.js';
3
+ import type { ExternalMigrationArtifact } from '../migration-artifact.js';
4
+ export interface MigrateOutputOptions {
5
+ root?: string;
6
+ gatewayUrl?: string;
7
+ json?: boolean;
8
+ verbose?: boolean;
9
+ quiet?: boolean;
10
+ fetchImpl?: typeof fetch;
11
+ }
12
+ export declare function newMigrationRequestId(): string;
13
+ export declare function runMigrateBuild(migrationPath: string, options?: MigrateOutputOptions): Promise<ExternalMigrationArtifact>;
14
+ export declare function assertExternalMigrationArtifact(run: SchemaChangeRunViewV1, artifact: ExternalMigrationArtifact): void;
15
+ type GuidedProgram = {
16
+ artifact?: ExternalMigrationArtifact;
17
+ recipe?: SchemaChangeRecipeV1;
18
+ runId?: string;
19
+ };
20
+ /** Compose the existing durable analysis and preparation; no second server state machine. */
21
+ export declare function runMigratePreview(target: SerializedSchema, program: GuidedProgram, options?: MigrateOutputOptions & {
22
+ requestId?: string;
23
+ }): Promise<void>;
24
+ export interface MigrationApplicationReadback {
25
+ projectId: string;
26
+ artifactId: string;
27
+ artifactDigest: string;
28
+ schemaVersion: string | null;
29
+ querySchemaFingerprint: string;
30
+ }
31
+ export declare function readMigrationApplication(args: {
32
+ appUrl: string;
33
+ projectId: string;
34
+ target: SerializedSchema;
35
+ signal?: AbortSignal;
36
+ fetchImpl?: typeof fetch;
37
+ }): Promise<{
38
+ url: string;
39
+ readback: MigrationApplicationReadback;
40
+ }>;
41
+ export declare function runMigratePairBuild(runId: string, target: SerializedSchema, input: {
42
+ phase: 'prepared' | 'serving';
43
+ appUrl: string;
44
+ previewProof?: string;
45
+ requestId?: string;
46
+ }, options?: MigrateOutputOptions): Promise<void>;
47
+ export declare function runMigrateGuidedActivate(target: SerializedSchema, program: GuidedProgram & {
48
+ appUrl?: string;
49
+ servingUrl?: string;
50
+ previewProof?: string;
51
+ yes?: boolean;
52
+ }, options?: MigrateOutputOptions & {
53
+ requestId?: string;
54
+ }): Promise<void>;
55
+ export declare function runMigrateAnalyze(targetSchema: SerializedSchema, recipe: SchemaChangeRecipeV1, options?: MigrateOutputOptions & {
56
+ requestId?: string;
57
+ }): Promise<void>;
58
+ export declare function runMigrateAnalyzeExternal(targetSchema: SerializedSchema, artifact: ExternalMigrationArtifact, options?: MigrateOutputOptions & {
59
+ requestId?: string;
60
+ }): Promise<void>;
61
+ export declare function runMigratePrepare(runId: string, analysisId: string, options?: MigrateOutputOptions & {
62
+ requestId?: string;
63
+ artifact?: ExternalMigrationArtifact;
64
+ }): Promise<void>;
65
+ export declare function runMigrateRunCommand(action: 'resume' | 'abort', runId: string, options?: MigrateOutputOptions & {
66
+ requestId?: string;
67
+ }): Promise<void>;
68
+ export declare function runMigrateAttest(body: Omit<SchemaChangeEvidenceBody, 'requestId'>, options?: MigrateOutputOptions & {
69
+ requestId?: string;
70
+ }): Promise<void>;
71
+ export declare function runMigrateActivate(runId: string, preparationId: string, evidenceId: string, options?: MigrateOutputOptions & {
72
+ requestId?: string;
73
+ }): Promise<void>;
74
+ export declare function runMigrateStatus(runId: string | undefined, options?: MigrateOutputOptions): Promise<void>;
75
+ export declare function runMigrateIssues(runId: string, analysisId: string, options?: MigrateOutputOptions & {
76
+ cursor?: string;
77
+ limit?: number;
78
+ }): Promise<void>;
79
+ export declare function runMigrateReceipt(runId: string, options?: MigrateOutputOptions): Promise<void>;
80
+ export {};