@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,178 @@
1
+ /**
2
+ * Hub API client (SPEC-1 Item #3 — session-expired UX).
3
+ *
4
+ * Every CLI command that calls a user-scoped Hub endpoint MUST route through
5
+ * `callHubApi`. Two things it guarantees:
6
+ *
7
+ * 1. **Lazy 401 recovery.** If the CLI JWT has expired, the Hub returns
8
+ * 401. Instead of dumping a stack trace and exiting, we print a single
9
+ * friendly line, launch device-auth login inline, and retry the original
10
+ * request ONCE. Second 401 on retry propagates — we do not loop.
11
+ *
12
+ * 2. **Structured error surface.** The Hub's Pattern D error envelope is
13
+ * `{ code, message }`; on non-2xx responses we throw a `StorystickError`
14
+ * whose message ends with the Hub's message, tacked with `(code)` when
15
+ * present. Command-level code prints these directly without extra
16
+ * wrapping.
17
+ *
18
+ * Edge cases (per spec §Edge cases):
19
+ *
20
+ * - 403 Forbidden → do NOT retry. Treated as "valid token, insufficient
21
+ * rights" (e.g. user removed from project mid-session, endpoint requires
22
+ * admin). Surface verbatim.
23
+ * - 429 on retry → do NOT retry again. The first call already burned the
24
+ * per-IP bucket; a second retry would just slam it harder.
25
+ * - User cancels re-auth (Ctrl-C, closes browser, polls expire) →
26
+ * `deviceAuthLogin` throws; we repackage as `StorystickError('Login
27
+ * cancelled.')` so commands can catch + exit 1 without a stack trace.
28
+ *
29
+ * This module deliberately takes NO dependency on commander/cac/pc — it is
30
+ * pure transport. The CLI command layer owns all user-facing printing.
31
+ */
32
+ import { deviceAuthLogin, getAuthToken, requiresExplicitAuthLogin, } from './auth-device.js';
33
+ import { StorystickError, storystickErrorFromResponse } from './cli-error.js';
34
+ import { getOutputMode, logWarning, promptDeviceAuth, stopSpinner, } from './output.js';
35
+ import { cliIssuerRequestUrl, DEFAULT_GATEWAY_URL, resolveCliRuntimeConfig } from './runtime-config.js';
36
+ import { fetchWithDeadline } from './transport.js';
37
+ import { contentShapeCapabilityHeaders } from '@storystick/types';
38
+ // `StorystickError` lives in `./cli-error.js` (ADR-072 §D3 / lock A6 —
39
+ // no re-export from this module; consumers import directly).
40
+ // ─────────────────────────────────────────────────────────────
41
+ // Entry point
42
+ // ─────────────────────────────────────────────────────────────
43
+ /**
44
+ * Call a Hub user-scoped endpoint with automatic retry-on-401.
45
+ *
46
+ * Success path: returns the parsed JSON body.
47
+ * Failure path: throws `StorystickError` with a clean, user-facing message.
48
+ */
49
+ export async function callHubApi(path, options = {}) {
50
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
51
+ const runtime = resolveCliRuntimeConfig({
52
+ rootDir: options.rootDir,
53
+ gatewayUrl: options.gatewayUrl ?? options.login?.gatewayUrl,
54
+ hubUrl: options.login?.hubUrl,
55
+ });
56
+ // Step 1 — obtain a token. If nothing is cached (first run of any
57
+ // user-scoped command after `storystick logout`), trigger device-auth
58
+ // upfront so the user sees the login prompt before the "it didn't work"
59
+ // message.
60
+ let token = getAuthToken(runtime.gatewayUrl);
61
+ if (!token) {
62
+ if (!mayStartInteractiveAuth(options, runtime.gatewayUrl))
63
+ throw authRequiredError(runtime.gatewayUrl);
64
+ token = await runDeviceAuthLogin(options, runtime);
65
+ }
66
+ // Step 2 — first attempt.
67
+ let res = await doCall(fetchImpl, runtime.gatewayUrl, path, token, options);
68
+ // Step 3 — on 401, re-auth once and retry.
69
+ if (res.status === 401) {
70
+ if (!mayStartInteractiveAuth(options, runtime.gatewayUrl))
71
+ throw authRequiredError(runtime.gatewayUrl);
72
+ // Single friendly line — keep it terse; the device-auth prompt prints
73
+ // next. Spec text: "Your session has expired. Logging you back in..."
74
+ logWarning('Your session has expired. Logging you back in...');
75
+ token = await runDeviceAuthLogin(options, runtime);
76
+ res = await doCall(fetchImpl, runtime.gatewayUrl, path, token, options);
77
+ // If the second call is ALSO 401, fall through to the generic error
78
+ // handling below — we do NOT loop.
79
+ }
80
+ if (!res.ok) {
81
+ throw await storystickErrorFromResponse(res, `HTTP ${res.status} ${res.statusText}`.trim());
82
+ }
83
+ // Some endpoints legitimately return 204 No Content. Guard the JSON parse.
84
+ if (res.status === 204)
85
+ return undefined;
86
+ if (options.responseType === 'bytes')
87
+ return new Uint8Array(await res.arrayBuffer());
88
+ return (await res.json());
89
+ }
90
+ function mayStartInteractiveAuth(options, gatewayUrl) {
91
+ // Project/env configuration can select a bound session, but cannot start a
92
+ // new device flow against another issuer behind the standard Hub prompt.
93
+ if (gatewayUrl !== DEFAULT_GATEWAY_URL || requiresExplicitAuthLogin(gatewayUrl))
94
+ return false;
95
+ return (options.interactiveAuth ??
96
+ (process.stdin.isTTY === true && getOutputMode() !== 'json'));
97
+ }
98
+ function authRequiredError(gatewayUrl) {
99
+ return new StorystickError(gatewayUrl === DEFAULT_GATEWAY_URL
100
+ ? 'Authentication required. Run `storystick login` in an interactive terminal.'
101
+ : `Authentication required for gateway ${gatewayUrl}. Run storystick login --gateway-url with this origin.`, {
102
+ code: 'AUTH_REQUIRED',
103
+ status: 401,
104
+ category: 'auth',
105
+ recoverable: true,
106
+ retryable: false,
107
+ });
108
+ }
109
+ // ─────────────────────────────────────────────────────────────
110
+ // Internals
111
+ // ─────────────────────────────────────────────────────────────
112
+ async function doCall(fetchImpl, gatewayUrl, path, bearer, options) {
113
+ const url = cliIssuerRequestUrl(gatewayUrl, path);
114
+ const method = options.method ?? 'GET';
115
+ const headers = {
116
+ Authorization: `Bearer ${bearer}`,
117
+ // Every CLI call identifies itself for CSRF + observability. The Hub's
118
+ // user-scoped endpoints accept `cli` as a valid client.
119
+ 'X-Storystick-Client': 'cli',
120
+ // User-scoped token issuance is also a Pattern D content client. Omitting
121
+ // this descriptor makes a current CLI indistinguishable from a legacy
122
+ // binary once the project advances to a stricter content-shape epoch.
123
+ ...contentShapeCapabilityHeaders('cli', options.contentShapeCapability),
124
+ };
125
+ if (options.body !== undefined) {
126
+ headers['Content-Type'] = 'application/json';
127
+ }
128
+ return await fetchWithDeadline(url, {
129
+ method,
130
+ redirect: 'error',
131
+ headers,
132
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
133
+ }, {
134
+ fetchImpl,
135
+ signal: options.signal,
136
+ timeoutMs: options.timeoutMs,
137
+ });
138
+ }
139
+ /**
140
+ * Run device-auth login, printing the verification prompt via the caller's
141
+ * hook (when provided) and otherwise via the shared `promptDeviceAuth`
142
+ * primitive. Any thrown error is repackaged as a `StorystickError`.
143
+ */
144
+ async function runDeviceAuthLogin(options, runtime) {
145
+ try {
146
+ return await deviceAuthLogin({
147
+ ...(options.login ?? {}),
148
+ hubUrl: options.login?.hubUrl ?? runtime.hubUrl,
149
+ gatewayUrl: runtime.gatewayUrl,
150
+ signal: options.signal ?? options.login?.signal,
151
+ timeoutMs: options.timeoutMs ?? options.login?.timeoutMs,
152
+ onPrompt: (data) => {
153
+ if (options.onAuthPrompt) {
154
+ options.onAuthPrompt(data);
155
+ return;
156
+ }
157
+ promptDeviceAuth(data);
158
+ },
159
+ });
160
+ }
161
+ catch (err) {
162
+ if (err instanceof Error && err.name === 'AbortError')
163
+ throw err;
164
+ const message = err instanceof Error ? err.message : String(err);
165
+ // Normalize the cancel/abort path — spec calls this "Login cancelled."
166
+ if (/cancel/i.test(message)) {
167
+ throw new StorystickError('Login cancelled.');
168
+ }
169
+ throw new StorystickError(message);
170
+ }
171
+ finally {
172
+ // `promptDeviceAuth` started the "Waiting for authorization…" spinner
173
+ // as a side effect of the onPrompt callback; close that bracket here
174
+ // so the next prompt / log line doesn't race the spinner frame.
175
+ // Idempotent — no-op if no spinner is live.
176
+ stopSpinner();
177
+ }
178
+ }
@@ -0,0 +1,98 @@
1
+ import type { StarterArtifactIdentity } from './starter-resolver.js';
2
+ import { StorystickError } from './cli-error.js';
3
+ export declare const INIT_OPERATION_RELATIVE_PATH: string;
4
+ export declare const INIT_PHASES: readonly ["P0", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10"];
5
+ export type InitPhase = (typeof INIT_PHASES)[number];
6
+ export type InitPhaseStatus = 'pending' | 'complete' | 'failed' | 'skipped';
7
+ export type InitIntent = {
8
+ kind: 'create';
9
+ projectName: string;
10
+ requestedTeamShortId?: string;
11
+ requestedNewTeamName?: string;
12
+ } | {
13
+ kind: 'link';
14
+ requestedShortId: string;
15
+ };
16
+ export interface InitOperationIdentity {
17
+ targetDir: string;
18
+ intent: InitIntent;
19
+ strategy: 'starter' | 'existing-next';
20
+ starter: {
21
+ catalogVersion: 2;
22
+ id: string;
23
+ release: string;
24
+ framework: 'next' | 'astro';
25
+ source: StarterArtifactIdentity['source'];
26
+ integrity: string;
27
+ localDirectory?: string;
28
+ };
29
+ origins: string[];
30
+ originMode: 'append' | 'default-if-empty';
31
+ locale: string;
32
+ siteUrl?: string;
33
+ force: boolean;
34
+ }
35
+ export interface InitOperationReceipt extends InitOperationIdentity {
36
+ version: 1;
37
+ operationId: string;
38
+ phase: InitPhase;
39
+ outcome: 'running' | 'partial' | 'failed' | 'complete';
40
+ phases: Record<InitPhase, InitPhaseStatus>;
41
+ projectShortId?: string;
42
+ projectName?: string;
43
+ newTeamName?: string;
44
+ selectedTeam?: {
45
+ shortId: string;
46
+ name: string;
47
+ };
48
+ endpoint?: string;
49
+ keyPairVersion?: number;
50
+ projectionSeq?: number;
51
+ schemaVersion?: string;
52
+ createdAt: number;
53
+ updatedAt: number;
54
+ }
55
+ export interface PreparedBootstrap {
56
+ receiptPatch: {
57
+ selectedTeam?: {
58
+ shortId: string;
59
+ name: string;
60
+ };
61
+ projectShortId: string;
62
+ keyPairVersion: number;
63
+ };
64
+ credentials: {
65
+ apiKey: string;
66
+ publishableKey: string;
67
+ };
68
+ }
69
+ export interface InitOrchestratorDependencies<TPrepared> {
70
+ planLocal(receipt: InitOperationReceipt): Promise<void>;
71
+ materializeAndInstall(receipt: InitOperationReceipt): Promise<void>;
72
+ prepareGeneration(receipt: InitOperationReceipt): Promise<TPrepared>;
73
+ authorizeAndSelect(receipt: InitOperationReceipt): Promise<Partial<InitOperationReceipt>>;
74
+ recoverCommittedConnection(receipt: InitOperationReceipt): Promise<boolean>;
75
+ prepareBootstrap(receipt: InitOperationReceipt): Promise<PreparedBootstrap>;
76
+ commitConnection(receipt: InitOperationReceipt, prepared: PreparedBootstrap): Promise<void>;
77
+ pushSchema(receipt: InitOperationReceipt, prepared: TPrepared): Promise<{
78
+ schemaVersion: string;
79
+ projectionSeq?: number;
80
+ }>;
81
+ commitGeneration(receipt: InitOperationReceipt, prepared: TPrepared): Promise<void>;
82
+ acknowledge(receipt: InitOperationReceipt): Promise<void>;
83
+ initializeGit(receipt: InitOperationReceipt): Promise<void>;
84
+ }
85
+ export declare class InitOrchestrationError extends StorystickError {
86
+ readonly receipt: InitOperationReceipt;
87
+ readonly resumeCommand: string;
88
+ constructor(cause: unknown, receipt: InitOperationReceipt);
89
+ }
90
+ export declare function runInitOrchestrator<TPrepared>(args: {
91
+ identity: InitOperationIdentity;
92
+ operationId?: string;
93
+ dependencies: InitOrchestratorDependencies<TPrepared>;
94
+ }): Promise<InitOperationReceipt>;
95
+ export declare function openInitOperation(identity: InitOperationIdentity, operationId?: string): InitOperationReceipt;
96
+ export declare function readInitOperation(targetDir: string): InitOperationReceipt | null;
97
+ export declare function initResumeCommand(receipt: InitOperationReceipt): string;
98
+ export declare function shellQuote(value: string): string;