stageflow 0.1.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 (180) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/dist/agent/activity.d.ts +76 -0
  4. package/dist/agent/activity.js +89 -0
  5. package/dist/agent/activityObserver.d.ts +17 -0
  6. package/dist/agent/activityObserver.js +66 -0
  7. package/dist/agent/cursorExtension.d.ts +12 -0
  8. package/dist/agent/cursorExtension.js +88 -0
  9. package/dist/agent/cursorProvider.d.ts +14 -0
  10. package/dist/agent/cursorProvider.js +139 -0
  11. package/dist/agent/fakeAgent.d.ts +29 -0
  12. package/dist/agent/fakeAgent.js +295 -0
  13. package/dist/agent/oauthSessionManager.d.ts +33 -0
  14. package/dist/agent/oauthSessionManager.js +214 -0
  15. package/dist/agent/piAdapter.d.ts +128 -0
  16. package/dist/agent/piAdapter.js +967 -0
  17. package/dist/agent/port.d.ts +63 -0
  18. package/dist/agent/port.js +46 -0
  19. package/dist/agent/providerAuth.d.ts +101 -0
  20. package/dist/agent/providerAuth.js +282 -0
  21. package/dist/agent/providerSupport.d.ts +28 -0
  22. package/dist/agent/providerSupport.js +19 -0
  23. package/dist/cli/providersCommand.d.ts +11 -0
  24. package/dist/cli/providersCommand.js +275 -0
  25. package/dist/cli/terminalAuthInteraction.d.ts +8 -0
  26. package/dist/cli/terminalAuthInteraction.js +65 -0
  27. package/dist/cli/terminalSecret.d.ts +2 -0
  28. package/dist/cli/terminalSecret.js +67 -0
  29. package/dist/cli/validateCommand.d.ts +11 -0
  30. package/dist/cli/validateCommand.js +81 -0
  31. package/dist/cli/validateOutput.d.ts +8 -0
  32. package/dist/cli/validateOutput.js +75 -0
  33. package/dist/cli.d.ts +2 -0
  34. package/dist/cli.js +273 -0
  35. package/dist/config/createPipeline.d.ts +31 -0
  36. package/dist/config/createPipeline.js +255 -0
  37. package/dist/config/createStage.d.ts +25 -0
  38. package/dist/config/createStage.js +181 -0
  39. package/dist/config/listConfig.d.ts +33 -0
  40. package/dist/config/listConfig.js +195 -0
  41. package/dist/config/listExtensions.d.ts +26 -0
  42. package/dist/config/listExtensions.js +50 -0
  43. package/dist/config/listSkills.d.ts +29 -0
  44. package/dist/config/listSkills.js +55 -0
  45. package/dist/config/loadOutcome.d.ts +27 -0
  46. package/dist/config/loadOutcome.js +6 -0
  47. package/dist/config/loadPipeline.d.ts +25 -0
  48. package/dist/config/loadPipeline.js +196 -0
  49. package/dist/config/loadStage.d.ts +4 -0
  50. package/dist/config/loadStage.js +127 -0
  51. package/dist/config/loadTask.d.ts +7 -0
  52. package/dist/config/loadTask.js +69 -0
  53. package/dist/config/readYamlObject.d.ts +1 -0
  54. package/dist/config/readYamlObject.js +5 -0
  55. package/dist/config/resolvePipelineDag.d.ts +12 -0
  56. package/dist/config/resolvePipelineDag.js +282 -0
  57. package/dist/config/validateCatalog.d.ts +36 -0
  58. package/dist/config/validateCatalog.js +322 -0
  59. package/dist/envelope/check.d.ts +3 -0
  60. package/dist/envelope/check.js +52 -0
  61. package/dist/envelope/payloadSchema.d.ts +10 -0
  62. package/dist/envelope/payloadSchema.js +91 -0
  63. package/dist/hitl/qaTrail.d.ts +31 -0
  64. package/dist/hitl/qaTrail.js +65 -0
  65. package/dist/index.d.ts +13 -0
  66. package/dist/index.js +7 -0
  67. package/dist/mcp/projectRun.d.ts +37 -0
  68. package/dist/mcp/projectRun.js +57 -0
  69. package/dist/mcp/readArtifact.d.ts +2 -0
  70. package/dist/mcp/readArtifact.js +46 -0
  71. package/dist/mcp/server.d.ts +7 -0
  72. package/dist/mcp/server.js +26 -0
  73. package/dist/mcp/tools.d.ts +9 -0
  74. package/dist/mcp/tools.js +96 -0
  75. package/dist/package-meta.d.ts +1 -0
  76. package/dist/package-meta.js +1 -0
  77. package/dist/prompt/priorEnvelope.d.ts +2 -0
  78. package/dist/prompt/priorEnvelope.js +5 -0
  79. package/dist/runstore/catalog.d.ts +8 -0
  80. package/dist/runstore/catalog.js +16 -0
  81. package/dist/runstore/createStore.d.ts +15 -0
  82. package/dist/runstore/createStore.js +23 -0
  83. package/dist/runstore/disk/DiskRunStore.d.ts +30 -0
  84. package/dist/runstore/disk/DiskRunStore.js +238 -0
  85. package/dist/runstore/disk/catalogHelpers.d.ts +11 -0
  86. package/dist/runstore/disk/catalogHelpers.js +102 -0
  87. package/dist/runstore/layout.d.ts +22 -0
  88. package/dist/runstore/layout.js +58 -0
  89. package/dist/runstore/paths.d.ts +7 -0
  90. package/dist/runstore/paths.js +60 -0
  91. package/dist/runstore/pipelineDagSnapshot.d.ts +5 -0
  92. package/dist/runstore/pipelineDagSnapshot.js +43 -0
  93. package/dist/runstore/port.d.ts +146 -0
  94. package/dist/runstore/port.js +28 -0
  95. package/dist/runstore/runProjection.d.ts +6 -0
  96. package/dist/runstore/runProjection.js +102 -0
  97. package/dist/runstore/sqlite/SqliteRunStore.d.ts +48 -0
  98. package/dist/runstore/sqlite/SqliteRunStore.js +460 -0
  99. package/dist/runstore/sqlite/migrateFromDisk.d.ts +5 -0
  100. package/dist/runstore/sqlite/migrateFromDisk.js +108 -0
  101. package/dist/runstore/sqlite/schema.d.ts +1 -0
  102. package/dist/runstore/sqlite/schema.js +59 -0
  103. package/dist/runstore/stageExecution.d.ts +13 -0
  104. package/dist/runstore/stageExecution.js +22 -0
  105. package/dist/runstore/stageSnapshot.d.ts +2 -0
  106. package/dist/runstore/stageSnapshot.js +46 -0
  107. package/dist/runstore/syntheticStageSnapshot.d.ts +2 -0
  108. package/dist/runstore/syntheticStageSnapshot.js +10 -0
  109. package/dist/runstore/trackProjection.d.ts +7 -0
  110. package/dist/runstore/trackProjection.js +94 -0
  111. package/dist/runstore/workspaceLayout.d.ts +31 -0
  112. package/dist/runstore/workspaceLayout.js +172 -0
  113. package/dist/runtime/answerResume.d.ts +16 -0
  114. package/dist/runtime/answerResume.js +22 -0
  115. package/dist/runtime/credentialBinding.d.ts +16 -0
  116. package/dist/runtime/credentialBinding.js +80 -0
  117. package/dist/runtime/dagTraversal.d.ts +3 -0
  118. package/dist/runtime/dagTraversal.js +26 -0
  119. package/dist/runtime/envelopeRouting.d.ts +20 -0
  120. package/dist/runtime/envelopeRouting.js +59 -0
  121. package/dist/runtime/hitlSeams.d.ts +38 -0
  122. package/dist/runtime/hitlSeams.js +2 -0
  123. package/dist/runtime/pipelineRunner.d.ts +77 -0
  124. package/dist/runtime/pipelineRunner.js +141 -0
  125. package/dist/runtime/pipelineScheduler.d.ts +90 -0
  126. package/dist/runtime/pipelineScheduler.js +494 -0
  127. package/dist/runtime/pipelineValidationError.d.ts +6 -0
  128. package/dist/runtime/pipelineValidationError.js +8 -0
  129. package/dist/runtime/resumeReconstruct.d.ts +33 -0
  130. package/dist/runtime/resumeReconstruct.js +144 -0
  131. package/dist/runtime/runManager.d.ts +129 -0
  132. package/dist/runtime/runManager.js +818 -0
  133. package/dist/runtime/runRetryCoordinator.d.ts +85 -0
  134. package/dist/runtime/runRetryCoordinator.js +372 -0
  135. package/dist/runtime/settingsFile.d.ts +17 -0
  136. package/dist/runtime/settingsFile.js +88 -0
  137. package/dist/runtime/stageAttemptBootstrap.d.ts +40 -0
  138. package/dist/runtime/stageAttemptBootstrap.js +82 -0
  139. package/dist/runtime/stageAttemptContext.d.ts +11 -0
  140. package/dist/runtime/stageAttemptContext.js +26 -0
  141. package/dist/runtime/stageConcurrency.d.ts +10 -0
  142. package/dist/runtime/stageConcurrency.js +51 -0
  143. package/dist/runtime/stageHitl.d.ts +95 -0
  144. package/dist/runtime/stageHitl.js +198 -0
  145. package/dist/runtime/stageProcessLauncher.d.ts +45 -0
  146. package/dist/runtime/stageProcessLauncher.js +199 -0
  147. package/dist/runtime/stageRecovery.d.ts +10 -0
  148. package/dist/runtime/stageRecovery.js +43 -0
  149. package/dist/runtime/stageRoots.d.ts +22 -0
  150. package/dist/runtime/stageRoots.js +93 -0
  151. package/dist/runtime/stageRunner.d.ts +59 -0
  152. package/dist/runtime/stageRunner.js +221 -0
  153. package/dist/runtime/stageWorker.d.ts +6 -0
  154. package/dist/runtime/stageWorker.js +109 -0
  155. package/dist/runtime/stageWorkerProtocol.d.ts +27 -0
  156. package/dist/runtime/stageWorkerProtocol.js +16 -0
  157. package/dist/runtime/taskInput.d.ts +15 -0
  158. package/dist/runtime/taskInput.js +38 -0
  159. package/dist/server/http.d.ts +31 -0
  160. package/dist/server/http.js +561 -0
  161. package/dist/server/providerRoutes.d.ts +15 -0
  162. package/dist/server/providerRoutes.js +186 -0
  163. package/dist/tools/askOperator.d.ts +185 -0
  164. package/dist/tools/askOperator.js +409 -0
  165. package/dist/tools/emitStageEnvelope.d.ts +42 -0
  166. package/dist/tools/emitStageEnvelope.js +61 -0
  167. package/dist/tools/writeStageArtifact.d.ts +26 -0
  168. package/dist/tools/writeStageArtifact.js +103 -0
  169. package/dist/types/envelope.d.ts +12 -0
  170. package/dist/types/envelope.js +6 -0
  171. package/dist/types/pipeline.d.ts +25 -0
  172. package/dist/types/pipeline.js +1 -0
  173. package/dist/types/stage.d.ts +12 -0
  174. package/dist/types/stage.js +6 -0
  175. package/dist/types/task.d.ts +7 -0
  176. package/dist/types/task.js +1 -0
  177. package/dist/ui/assets/index-Cry0Tpfx.js +118 -0
  178. package/dist/ui/assets/index-DCsDopak.css +1 -0
  179. package/dist/ui/index.html +21 -0
  180. package/package.json +72 -0
@@ -0,0 +1,63 @@
1
+ import type { StageEnvelope } from "../types/envelope.js";
2
+ import type { StageConfig } from "../types/stage.js";
3
+ import type { TaskFile } from "../types/task.js";
4
+ import type { StageActivityEvent } from "./activity.js";
5
+ import type { StageRoots } from "../runtime/stageRoots.js";
6
+ /** Opaque to runtime; adapters interpret. */
7
+ export type StageResumeToken = string;
8
+ export type StageRunInput = {
9
+ roots: StageRoots;
10
+ stage: StageConfig;
11
+ task: TaskFile;
12
+ priorEnvelope: StageEnvelope | null;
13
+ timeoutMs?: number;
14
+ resumeToken?: StageResumeToken;
15
+ /** Optional observe hook; HITL wait/answer uses openStage beside this. */
16
+ onActivity?: (event: StageActivityEvent) => void;
17
+ skillFilePath?: string;
18
+ };
19
+ export type StageRunResult = {
20
+ ok: true;
21
+ envelope: StageEnvelope;
22
+ } | {
23
+ ok: false;
24
+ reason: string;
25
+ envelope?: StageEnvelope;
26
+ };
27
+ /** Opaque wait-request blob; T2 owns concrete prompt shapes later. */
28
+ export type OpaqueWaitRequest = unknown;
29
+ /** Opaque answer blob; T2 owns concrete answer shapes later. */
30
+ export type OpaqueAnswer = unknown;
31
+ export type StageHandleCloseOptions = {
32
+ /** Keep an in-flight ask_operator tool call open for cross-process resume. */
33
+ park?: boolean;
34
+ };
35
+ export type StageHandleEvent = {
36
+ status: "waiting_for_input";
37
+ request: OpaqueWaitRequest;
38
+ } | {
39
+ status: "completed";
40
+ result: StageRunResult;
41
+ };
42
+ /**
43
+ * Live stage session handle. Runtime pulls wait-or-complete via `next()`,
44
+ * unblocks with `deliverAnswer`, and releases with `close`.
45
+ */
46
+ export interface StageHandle {
47
+ readonly stageId: string;
48
+ next(): Promise<StageHandleEvent>;
49
+ deliverAnswer(answer: OpaqueAnswer): void;
50
+ close(options?: StageHandleCloseOptions): Promise<void>;
51
+ }
52
+ export interface AgentPort {
53
+ openStage(input: StageRunInput): StageHandle;
54
+ runStage(input: StageRunInput): Promise<StageRunResult>;
55
+ }
56
+ /** Thin non-HITL wrapper: open → next; unexpected wait fails closed. */
57
+ export declare function runStageViaOpen(port: Pick<AgentPort, "openStage">, input: StageRunInput): Promise<StageRunResult>;
58
+ /** Wrap a single-shot runner as a handle that never waits (Pi U2 shim). */
59
+ export declare function createCompletedOnlyStageHandle(options: {
60
+ stageId: string;
61
+ run: () => Promise<StageRunResult>;
62
+ }): StageHandle;
63
+ export declare const DEFAULT_STAGE_TIMEOUT_MS: number;
@@ -0,0 +1,46 @@
1
+ /** Thin non-HITL wrapper: open → next; unexpected wait fails closed. */
2
+ export async function runStageViaOpen(port, input) {
3
+ const handle = port.openStage(input);
4
+ try {
5
+ const event = await handle.next();
6
+ if (event.status === "waiting_for_input") {
7
+ return {
8
+ ok: false,
9
+ reason: "stage requested wait; use openStage/deliverAnswer",
10
+ };
11
+ }
12
+ return event.result;
13
+ }
14
+ finally {
15
+ await handle.close();
16
+ }
17
+ }
18
+ /** Wrap a single-shot runner as a handle that never waits (Pi U2 shim). */
19
+ export function createCompletedOnlyStageHandle(options) {
20
+ let settled;
21
+ let closed = false;
22
+ const runPromise = options.run().then((result) => {
23
+ settled = { status: "completed", result };
24
+ return settled;
25
+ });
26
+ return {
27
+ stageId: options.stageId,
28
+ async next() {
29
+ if (closed) {
30
+ return {
31
+ status: "completed",
32
+ result: { ok: false, reason: "stage handle closed" },
33
+ };
34
+ }
35
+ if (settled)
36
+ return settled;
37
+ return runPromise;
38
+ },
39
+ deliverAnswer() { },
40
+ async close() {
41
+ closed = true;
42
+ await runPromise.catch(() => undefined);
43
+ },
44
+ };
45
+ }
46
+ export const DEFAULT_STAGE_TIMEOUT_MS = 15 * 60 * 1000;
@@ -0,0 +1,101 @@
1
+ import type { AuthInteraction, Provider } from "@earendil-works/pi-ai";
2
+ import type { CredentialSource } from "../runtime/settingsFile.js";
3
+ export type ProviderSummary = {
4
+ id: string;
5
+ name: string;
6
+ supportsApiKey: boolean;
7
+ supportsOauth: boolean;
8
+ oauthLabel?: string;
9
+ };
10
+ export type LoginCapableProvider = {
11
+ id: string;
12
+ name: string;
13
+ apiKeyLogin: boolean;
14
+ oauth: boolean;
15
+ oauthLabel?: string;
16
+ };
17
+ export type ProviderAuthStatus = {
18
+ providerId: string;
19
+ configured: boolean;
20
+ authKind?: "api_key" | "oauth" | "none";
21
+ source?: string;
22
+ };
23
+ export type ProvidersListResult = {
24
+ authShell: "pi";
25
+ via: "pi";
26
+ providers: ProviderSummary[];
27
+ };
28
+ export type PiHomeDetectResult = {
29
+ piHomeUsable: boolean;
30
+ credentialSource?: CredentialSource;
31
+ provisional: boolean;
32
+ source: CredentialSource;
33
+ };
34
+ export type ProviderAuthRuntime = {
35
+ getProviders(): readonly Provider[];
36
+ getProvider(providerId: string): Provider | undefined;
37
+ getProviderAuthStatus(providerId: string): {
38
+ configured: boolean;
39
+ source?: string;
40
+ label?: string;
41
+ };
42
+ listCredentials(): Promise<readonly {
43
+ providerId: string;
44
+ type: "api_key" | "oauth";
45
+ }[]>;
46
+ checkAuth(providerId: string): Promise<{
47
+ source?: string;
48
+ type: "api_key" | "oauth";
49
+ } | undefined>;
50
+ login(providerId: string, type: "api_key" | "oauth", interaction: AuthInteraction): Promise<unknown>;
51
+ logout(providerId: string): Promise<void>;
52
+ };
53
+ export type CreateProviderAuthRuntime = (authPath: string) => Promise<ProviderAuthRuntime>;
54
+ export type MutationLock = {
55
+ withMutationLock<T>(fn: () => Promise<T>): Promise<T>;
56
+ };
57
+ export type ProviderAuthContext = {
58
+ createRuntime: CreateProviderAuthRuntime;
59
+ lock: MutationLock;
60
+ };
61
+ export declare function makeMutationLock(): MutationLock;
62
+ export declare class ProviderAuthError extends Error {
63
+ readonly status: number;
64
+ constructor(message: string, status: number);
65
+ }
66
+ export declare const defaultContext: ProviderAuthContext;
67
+ export declare function openRuntime(cwd: string, ctx?: ProviderAuthContext): Promise<ProviderAuthRuntime>;
68
+ export declare function isLoginCapable(provider: {
69
+ auth?: {
70
+ oauth?: unknown;
71
+ apiKey?: {
72
+ login?: unknown;
73
+ };
74
+ };
75
+ }): boolean;
76
+ export declare function listLoginCapableProviders(runtime: Pick<ProviderAuthRuntime, "getProviders">): LoginCapableProvider[];
77
+ export declare function statusForProvider(runtime: ProviderAuthRuntime, providerId: string): Promise<ProviderAuthStatus>;
78
+ export declare function listProviders(cwd: string, ctx?: ProviderAuthContext): Promise<ProvidersListResult>;
79
+ export declare function getAuthStatus(cwd: string, providerId?: string, ctx?: ProviderAuthContext): Promise<ProviderAuthStatus | ProviderAuthStatus[]>;
80
+ export declare function loginWithApiKey(cwd: string, providerId: string, apiKey: string, ctx?: ProviderAuthContext): Promise<ProviderAuthStatus>;
81
+ export type OauthLoginResult = {
82
+ provider: ProviderAuthStatus;
83
+ warning?: string;
84
+ };
85
+ export declare function loginWithOauth(cwd: string, providerId: string, interaction: AuthInteraction, ctx?: ProviderAuthContext): Promise<OauthLoginResult>;
86
+ export declare function logoutProvider(cwd: string, providerId: string, ctx?: ProviderAuthContext): Promise<ProviderAuthStatus>;
87
+ export declare function detectPiHome(cwd: string): PiHomeDetectResult;
88
+ export declare function getCredentialSourceSettings(cwd: string): {
89
+ credentialSource?: CredentialSource;
90
+ binding: {
91
+ source: CredentialSource;
92
+ provisional: boolean;
93
+ };
94
+ };
95
+ export declare function setCredentialSource(cwd: string, credentialSource: unknown): {
96
+ credentialSource: CredentialSource;
97
+ binding: {
98
+ source: CredentialSource;
99
+ provisional: boolean;
100
+ };
101
+ };
@@ -0,0 +1,282 @@
1
+ import { CredentialSynchronizationError, ModelRuntime, } from "@earendil-works/pi-coding-agent";
2
+ import { isUsableAuthFile, parseCredentialSource, piHomeAuthPath, readCredentialSourceFromFile, resolveCredentialBinding, writeCredentialSourceToFile, } from "../runtime/credentialBinding.js";
3
+ export function makeMutationLock() {
4
+ let tail = Promise.resolve();
5
+ return {
6
+ withMutationLock(fn) {
7
+ const run = tail.then(fn, fn);
8
+ tail = run.then(() => undefined, () => undefined);
9
+ return run;
10
+ },
11
+ };
12
+ }
13
+ export class ProviderAuthError extends Error {
14
+ status;
15
+ constructor(message, status) {
16
+ super(message);
17
+ this.name = "ProviderAuthError";
18
+ this.status = status;
19
+ }
20
+ }
21
+ const defaultCreateRuntime = async (authPath) => ModelRuntime.create({ authPath, refreshOnCreate: false });
22
+ export const defaultContext = {
23
+ createRuntime: defaultCreateRuntime,
24
+ lock: makeMutationLock(),
25
+ };
26
+ export async function openRuntime(cwd, ctx = defaultContext) {
27
+ const binding = resolveCredentialBinding(cwd);
28
+ return ctx.createRuntime(binding.authPath);
29
+ }
30
+ export function isLoginCapable(provider) {
31
+ return (provider.auth?.oauth != null || provider.auth?.apiKey?.login != null);
32
+ }
33
+ export function listLoginCapableProviders(runtime) {
34
+ return runtime
35
+ .getProviders()
36
+ .filter(isLoginCapable)
37
+ .map((provider) => {
38
+ const apiKeyLogin = provider.auth?.apiKey?.login != null;
39
+ const oauth = provider.auth?.oauth != null;
40
+ const oauthName = oauth && provider.auth?.oauth && "name" in provider.auth.oauth
41
+ ? provider.auth.oauth.name
42
+ : undefined;
43
+ return {
44
+ id: provider.id,
45
+ name: provider.name,
46
+ apiKeyLogin,
47
+ oauth,
48
+ ...(typeof oauthName === "string" && oauthName.length > 0
49
+ ? { oauthLabel: oauthName }
50
+ : {}),
51
+ };
52
+ });
53
+ }
54
+ function mapProvider(provider) {
55
+ const supportsApiKey = provider.auth.apiKey?.login != null;
56
+ const supportsOauth = provider.auth.oauth !== undefined;
57
+ const oauthLabel = supportsOauth && typeof provider.auth.oauth?.name === "string"
58
+ ? provider.auth.oauth.name
59
+ : undefined;
60
+ return {
61
+ id: provider.id,
62
+ name: provider.name,
63
+ supportsApiKey,
64
+ supportsOauth,
65
+ ...(oauthLabel !== undefined && oauthLabel.length > 0
66
+ ? { oauthLabel }
67
+ : {}),
68
+ };
69
+ }
70
+ function safeStatus(providerId, status, authKind) {
71
+ const out = {
72
+ providerId,
73
+ configured: status.configured,
74
+ };
75
+ if (authKind !== undefined) {
76
+ out.authKind = authKind;
77
+ }
78
+ if (typeof status.source === "string" && status.source.length > 0) {
79
+ out.source = status.source;
80
+ }
81
+ else if (typeof status.label === "string" && status.label.length > 0) {
82
+ out.source = status.label;
83
+ }
84
+ return out;
85
+ }
86
+ export async function statusForProvider(runtime, providerId) {
87
+ const status = runtime.getProviderAuthStatus(providerId);
88
+ const creds = await runtime.listCredentials();
89
+ const stored = creds.find((c) => c.providerId === providerId);
90
+ let authKind = "none";
91
+ if (stored) {
92
+ authKind = stored.type;
93
+ }
94
+ else {
95
+ try {
96
+ const checked = await runtime.checkAuth(providerId);
97
+ if (checked?.type === "api_key" || checked?.type === "oauth") {
98
+ authKind = checked.type;
99
+ }
100
+ }
101
+ catch {
102
+ // ignore check failures for status DTO
103
+ }
104
+ }
105
+ const configured = Boolean(stored) || status.configured;
106
+ const mapped = safeStatus(providerId, {
107
+ configured,
108
+ source: stored ? (status.source ?? "stored") : status.source,
109
+ label: status.label,
110
+ }, authKind);
111
+ return mapped;
112
+ }
113
+ export async function listProviders(cwd, ctx = defaultContext) {
114
+ const runtime = await openRuntime(cwd, ctx);
115
+ return {
116
+ authShell: "pi",
117
+ via: "pi",
118
+ providers: runtime
119
+ .getProviders()
120
+ .filter(isLoginCapable)
121
+ .map(mapProvider),
122
+ };
123
+ }
124
+ export async function getAuthStatus(cwd, providerId, ctx = defaultContext) {
125
+ const runtime = await openRuntime(cwd, ctx);
126
+ if (providerId !== undefined) {
127
+ if (!runtime.getProvider(providerId)) {
128
+ throw new ProviderAuthError("Provider not found", 404);
129
+ }
130
+ return statusForProvider(runtime, providerId);
131
+ }
132
+ const providers = runtime.getProviders();
133
+ const out = [];
134
+ for (const provider of providers) {
135
+ out.push(await statusForProvider(runtime, provider.id));
136
+ }
137
+ return out;
138
+ }
139
+ function apiKeyInteraction(apiKey) {
140
+ return {
141
+ async prompt(prompt) {
142
+ if (prompt.type === "secret") {
143
+ return apiKey;
144
+ }
145
+ throw new ProviderAuthError("Unexpected auth prompt for api_key login", 400);
146
+ },
147
+ notify() { },
148
+ };
149
+ }
150
+ function mapLoginError(err, providerId) {
151
+ if (err instanceof ProviderAuthError) {
152
+ throw err;
153
+ }
154
+ if (err instanceof CredentialSynchronizationError) {
155
+ throw new ProviderAuthError(`Credential sync failed for provider ${providerId}`, 502);
156
+ }
157
+ throw new ProviderAuthError("Provider login failed", 500);
158
+ }
159
+ export async function loginWithApiKey(cwd, providerId, apiKey, ctx = defaultContext) {
160
+ if (typeof apiKey !== "string" || apiKey.trim().length === 0) {
161
+ throw new ProviderAuthError("apiKey is required", 400);
162
+ }
163
+ return ctx.lock.withMutationLock(async () => {
164
+ const runtime = await openRuntime(cwd, ctx);
165
+ const provider = runtime.getProvider(providerId);
166
+ if (!provider) {
167
+ throw new ProviderAuthError("Provider not found", 404);
168
+ }
169
+ if (typeof provider.auth.apiKey?.login !== "function") {
170
+ throw new ProviderAuthError("Provider does not support api_key login", 400);
171
+ }
172
+ try {
173
+ await runtime.login(providerId, "api_key", apiKeyInteraction(apiKey));
174
+ }
175
+ catch (err) {
176
+ mapLoginError(err, providerId);
177
+ }
178
+ return statusForProvider(runtime, providerId);
179
+ });
180
+ }
181
+ export async function loginWithOauth(cwd, providerId, interaction, ctx = defaultContext) {
182
+ return ctx.lock.withMutationLock(async () => {
183
+ const runtime = await openRuntime(cwd, ctx);
184
+ const provider = runtime.getProvider(providerId);
185
+ if (!provider) {
186
+ throw new ProviderAuthError("Provider not found", 404);
187
+ }
188
+ if (provider.auth.oauth === undefined) {
189
+ throw new ProviderAuthError("Provider does not support oauth login", 400);
190
+ }
191
+ try {
192
+ await runtime.login(providerId, "oauth", interaction);
193
+ return { provider: await statusForProvider(runtime, providerId) };
194
+ }
195
+ catch (err) {
196
+ if (err instanceof CredentialSynchronizationError) {
197
+ let status;
198
+ try {
199
+ status = await statusForProvider(runtime, providerId);
200
+ }
201
+ catch {
202
+ status = {
203
+ providerId,
204
+ configured: true,
205
+ authKind: "oauth",
206
+ };
207
+ }
208
+ return {
209
+ provider: status.configured
210
+ ? status
211
+ : {
212
+ ...status,
213
+ configured: true,
214
+ authKind: status.authKind ?? "oauth",
215
+ },
216
+ warning: "Connected, but model catalog sync failed. Credentials were saved — you can retry later without disconnecting.",
217
+ };
218
+ }
219
+ if (interaction.signal?.aborted) {
220
+ throw new ProviderAuthError("Login cancelled", 400);
221
+ }
222
+ if (err instanceof Error && /cancel/i.test(err.message)) {
223
+ throw new ProviderAuthError("Login cancelled", 400);
224
+ }
225
+ mapLoginError(err, providerId);
226
+ }
227
+ });
228
+ }
229
+ export async function logoutProvider(cwd, providerId, ctx = defaultContext) {
230
+ return ctx.lock.withMutationLock(async () => {
231
+ const runtime = await openRuntime(cwd, ctx);
232
+ if (!runtime.getProvider(providerId)) {
233
+ throw new ProviderAuthError("Provider not found", 404);
234
+ }
235
+ try {
236
+ await runtime.logout(providerId);
237
+ }
238
+ catch (err) {
239
+ if (err instanceof CredentialSynchronizationError) {
240
+ throw new ProviderAuthError(`Credential sync failed for provider ${providerId}`, 502);
241
+ }
242
+ throw new ProviderAuthError("Provider logout failed", 500);
243
+ }
244
+ return statusForProvider(runtime, providerId);
245
+ });
246
+ }
247
+ export function detectPiHome(cwd) {
248
+ const binding = resolveCredentialBinding(cwd);
249
+ const persisted = readCredentialSourceFromFile(cwd);
250
+ return {
251
+ piHomeUsable: isUsableAuthFile(piHomeAuthPath()),
252
+ ...(persisted !== undefined ? { credentialSource: persisted } : {}),
253
+ provisional: binding.provisional,
254
+ source: binding.source,
255
+ };
256
+ }
257
+ export function getCredentialSourceSettings(cwd) {
258
+ const persisted = readCredentialSourceFromFile(cwd);
259
+ const binding = resolveCredentialBinding(cwd);
260
+ return {
261
+ ...(persisted !== undefined ? { credentialSource: persisted } : {}),
262
+ binding: {
263
+ source: binding.source,
264
+ provisional: binding.provisional,
265
+ },
266
+ };
267
+ }
268
+ export function setCredentialSource(cwd, credentialSource) {
269
+ const parsed = parseCredentialSource(credentialSource);
270
+ if (parsed === undefined) {
271
+ throw new ProviderAuthError('credentialSource must be "pi_home" or "sf_owned"', 400);
272
+ }
273
+ writeCredentialSourceToFile(cwd, parsed);
274
+ const binding = resolveCredentialBinding(cwd);
275
+ return {
276
+ credentialSource: parsed,
277
+ binding: {
278
+ source: binding.source,
279
+ provisional: binding.provisional,
280
+ },
281
+ };
282
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Optional hooks for model providers that need more than Pi's built-in
3
+ * auth/models (typically an allowlisted extension that calls registerProvider).
4
+ *
5
+ * PiAgentAdapter stays provider-agnostic: it only consumes this interface.
6
+ * Concrete providers (Cursor today) live in their own modules and register here.
7
+ */
8
+ export type ProviderPrepareResult = {
9
+ /** Absolute paths passed to DefaultResourceLoader.additionalExtensionPaths. */
10
+ extensionPaths: string[];
11
+ /** Optional env / process restore after the stage ends. */
12
+ restore?: () => void;
13
+ /** Failure before session create (missing extension, bad config, …). */
14
+ error?: string;
15
+ };
16
+ export type StageProviderSupport = {
17
+ /** Stable id for logs/docs (e.g. "cursor"). */
18
+ id: string;
19
+ matches(modelRef: string): boolean;
20
+ prepare(modelRef: string): ProviderPrepareResult;
21
+ /**
22
+ * Extra guidance for how the model must call emit_stage_envelope
23
+ * (e.g. MCP bridge name). When omitted, the generic Pi tool name is used.
24
+ */
25
+ emitToolHint?(toolName: string): string;
26
+ };
27
+ export declare function registerProviderSupport(support: StageProviderSupport): void;
28
+ export declare function findProviderSupport(modelRef: string): StageProviderSupport | undefined;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Optional hooks for model providers that need more than Pi's built-in
3
+ * auth/models (typically an allowlisted extension that calls registerProvider).
4
+ *
5
+ * PiAgentAdapter stays provider-agnostic: it only consumes this interface.
6
+ * Concrete providers (Cursor today) live in their own modules and register here.
7
+ */
8
+ const registry = [];
9
+ export function registerProviderSupport(support) {
10
+ const existing = registry.findIndex((entry) => entry.id === support.id);
11
+ if (existing >= 0) {
12
+ registry[existing] = support;
13
+ return;
14
+ }
15
+ registry.push(support);
16
+ }
17
+ export function findProviderSupport(modelRef) {
18
+ return registry.find((entry) => entry.matches(modelRef));
19
+ }
@@ -0,0 +1,11 @@
1
+ import type { AuthInteraction } from "@earendil-works/pi-ai";
2
+ import { type ProviderAuthContext } from "../agent/providerAuth.js";
3
+ export declare const PROVIDERS_USAGE = "Usage:\n sf providers list\n sf providers status [--provider <id>]\n sf providers detect\n sf providers source [get | set <pi_home|sf_owned>]\n sf providers login <providerId> [--type api_key|oauth] [--api-key-env <VAR>]\n sf providers logout <providerId>";
4
+ export type ProvidersCommandIo = {
5
+ log: (line: string) => void;
6
+ error: (line: string) => void;
7
+ env: NodeJS.ProcessEnv;
8
+ readSecret: (message: string) => Promise<string>;
9
+ createAuthInteraction: (signal: AbortSignal) => AuthInteraction;
10
+ };
11
+ export declare function runProvidersCommand(args: string[], cwd: string, io?: Partial<ProvidersCommandIo>, ctx?: ProviderAuthContext): Promise<number>;