reffy-cli 1.4.2 → 1.7.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/index.d.ts CHANGED
@@ -4,5 +4,5 @@ export { runDoctor } from "./doctor.js";
4
4
  export { createPlanScaffold } from "./plan.js";
5
5
  export { loadDotEnvIfPresent, parseDotEnv } from "./env.js";
6
6
  export { ensureGitignoreEntries } from "./gitignore.js";
7
- export { collectWorkspaceDocuments, ensureRemoteInit, extractWorkspaceIdentity, mergeRemoteConfig, PaseoRemoteClient, readRemoteConfig, resolveRemoteConfigPath, toCanonicalRemotePath, writeRemoteConfig, } from "./remote.js";
7
+ export { collectWorkspaceDocuments, ensureManagerInit, ensureWorkspaceTarget, extractWorkspaceSummaryIdentity, PaseoManagerClient, PaseoWorkspaceBackendClient, readRemoteConfig, resolveRemoteConfigPath, resolveRemoteTarget, resolveSelectedWorkspaceId, toCanonicalRemotePath, writeRemoteConfig, } from "./remote.js";
8
8
  export { listSpecs, showSpec } from "./spec-runtime.js";
package/dist/index.js CHANGED
@@ -4,5 +4,5 @@ export { runDoctor } from "./doctor.js";
4
4
  export { createPlanScaffold } from "./plan.js";
5
5
  export { loadDotEnvIfPresent, parseDotEnv } from "./env.js";
6
6
  export { ensureGitignoreEntries } from "./gitignore.js";
7
- export { collectWorkspaceDocuments, ensureRemoteInit, extractWorkspaceIdentity, mergeRemoteConfig, PaseoRemoteClient, readRemoteConfig, resolveRemoteConfigPath, toCanonicalRemotePath, writeRemoteConfig, } from "./remote.js";
7
+ export { collectWorkspaceDocuments, ensureManagerInit, ensureWorkspaceTarget, extractWorkspaceSummaryIdentity, PaseoManagerClient, PaseoWorkspaceBackendClient, readRemoteConfig, resolveRemoteConfigPath, resolveRemoteTarget, resolveSelectedWorkspaceId, toCanonicalRemotePath, writeRemoteConfig, } from "./remote.js";
8
8
  export { listSpecs, showSpec } from "./spec-runtime.js";
@@ -1,7 +1,11 @@
1
1
  import type { Manifest } from "./types.js";
2
2
  export declare const MANIFEST_VERSION = 1;
3
3
  export declare function allowedKindExtensions(): Record<string, string[]>;
4
- export declare function deriveManifestIdentity(repoRoot: string): Required<Pick<Manifest, "project_id" | "workspace_name">>;
4
+ export interface ManifestIdentityDefaults {
5
+ project_id: string;
6
+ workspace_ids: string[];
7
+ }
8
+ export declare function deriveManifestIdentity(repoRoot: string): ManifestIdentityDefaults;
5
9
  export declare function createManifest(repoRoot: string, now?: string): Manifest;
6
10
  export declare function normalizeManifest(raw: unknown, repoRoot: string): Manifest;
7
11
  export declare function inferArtifactType(filePath: string): {
package/dist/manifest.js CHANGED
@@ -13,6 +13,7 @@ const KIND_EXTENSIONS = {
13
13
  file: [],
14
14
  };
15
15
  const PROJECT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
16
+ const WORKSPACE_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
16
17
  const FALLBACK_WORKSPACE_ID = "reffy-workspace";
17
18
  function isObject(value) {
18
19
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -25,11 +26,22 @@ function isIsoDate(value) {
25
26
  function isProjectId(value) {
26
27
  return typeof value === "string" && PROJECT_ID_PATTERN.test(value);
27
28
  }
29
+ function isWorkspaceId(value) {
30
+ return typeof value === "string" && WORKSPACE_ID_PATTERN.test(value);
31
+ }
28
32
  function isWorkspaceName(value) {
29
33
  return (typeof value === "string" &&
30
34
  value.trim().length > 0 &&
31
35
  !/[\\/\r\n\t]/.test(value));
32
36
  }
37
+ function slugifyWorkspaceId(value) {
38
+ const slug = value
39
+ .trim()
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9]+/g, "-")
42
+ .replace(/^-+|-+$/g, "");
43
+ return slug.length > 0 ? slug : null;
44
+ }
33
45
  function slugifyIdentity(value) {
34
46
  return value
35
47
  .trim()
@@ -51,18 +63,33 @@ export function deriveManifestIdentity(repoRoot) {
51
63
  const identity = candidate || FALLBACK_WORKSPACE_ID;
52
64
  return {
53
65
  project_id: identity,
54
- workspace_name: identity,
66
+ workspace_ids: [identity],
55
67
  };
56
68
  }
57
69
  export function createManifest(repoRoot, now = new Date().toISOString()) {
70
+ const defaults = deriveManifestIdentity(repoRoot);
58
71
  return {
59
72
  version: MANIFEST_VERSION,
60
73
  created_at: now,
61
74
  updated_at: now,
62
- ...deriveManifestIdentity(repoRoot),
75
+ project_id: defaults.project_id,
76
+ workspace_ids: defaults.workspace_ids,
63
77
  artifacts: [],
64
78
  };
65
79
  }
80
+ function normalizeWorkspaceIds(raw, fallback) {
81
+ const explicit = Array.isArray(raw.workspace_ids)
82
+ ? raw.workspace_ids.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
83
+ : null;
84
+ if (explicit && explicit.length > 0) {
85
+ return Array.from(new Set(explicit.map((entry) => entry.trim())));
86
+ }
87
+ if (typeof raw.workspace_name === "string" && raw.workspace_name.trim().length > 0) {
88
+ const migrated = slugifyWorkspaceId(raw.workspace_name) ?? raw.workspace_name.trim();
89
+ return [migrated];
90
+ }
91
+ return [...fallback];
92
+ }
66
93
  export function normalizeManifest(raw, repoRoot) {
67
94
  const now = new Date().toISOString();
68
95
  const defaults = deriveManifestIdentity(repoRoot);
@@ -71,7 +98,8 @@ export function normalizeManifest(raw, repoRoot) {
71
98
  version: 0,
72
99
  created_at: now,
73
100
  updated_at: now,
74
- ...defaults,
101
+ project_id: defaults.project_id,
102
+ workspace_ids: defaults.workspace_ids,
75
103
  artifacts: raw,
76
104
  };
77
105
  }
@@ -79,12 +107,13 @@ export function normalizeManifest(raw, repoRoot) {
79
107
  return createManifest(repoRoot, now);
80
108
  }
81
109
  const artifacts = Array.isArray(raw.artifacts) ? raw.artifacts : [];
110
+ const workspaceIds = normalizeWorkspaceIds(raw, defaults.workspace_ids);
82
111
  return {
83
112
  version: typeof raw.version === "number" ? raw.version : MANIFEST_VERSION,
84
113
  created_at: typeof raw.created_at === "string" ? raw.created_at : now,
85
114
  updated_at: typeof raw.updated_at === "string" ? raw.updated_at : now,
86
115
  project_id: typeof raw.project_id === "string" ? raw.project_id : defaults.project_id,
87
- workspace_name: typeof raw.workspace_name === "string" ? raw.workspace_name : defaults.workspace_name,
116
+ workspace_ids: workspaceIds,
88
117
  artifacts,
89
118
  };
90
119
  }
@@ -183,8 +212,32 @@ export async function validateManifest(manifestPath, artifactsDir) {
183
212
  if (raw.project_id !== undefined && !isProjectId(raw.project_id)) {
184
213
  errors.push("project_id must be a non-empty kebab-case string");
185
214
  }
186
- if (raw.workspace_name !== undefined && !isWorkspaceName(raw.workspace_name)) {
187
- errors.push("workspace_name must be a non-empty string without path separators or control characters");
215
+ if (raw.workspace_ids !== undefined) {
216
+ if (!Array.isArray(raw.workspace_ids) || raw.workspace_ids.length === 0) {
217
+ errors.push("workspace_ids must be a non-empty array of kebab-case strings when provided");
218
+ }
219
+ else {
220
+ const seen = new Set();
221
+ raw.workspace_ids.forEach((entry, index) => {
222
+ if (!isWorkspaceId(entry)) {
223
+ errors.push(`workspace_ids[${index}] must be a non-empty kebab-case string`);
224
+ return;
225
+ }
226
+ if (seen.has(entry)) {
227
+ errors.push(`workspace_ids[${index}] duplicates "${entry}"`);
228
+ return;
229
+ }
230
+ seen.add(entry);
231
+ });
232
+ }
233
+ }
234
+ if (raw.workspace_name !== undefined) {
235
+ if (!isWorkspaceName(raw.workspace_name)) {
236
+ errors.push("workspace_name must be a non-empty string without path separators or control characters");
237
+ }
238
+ else if (raw.workspace_ids === undefined) {
239
+ warnings.push("workspace_name is deprecated; migrate to workspace_ids");
240
+ }
188
241
  }
189
242
  if (!Array.isArray(raw.artifacts)) {
190
243
  errors.push("artifacts must be an array");
package/dist/remote.d.ts CHANGED
@@ -1,21 +1,23 @@
1
- import type { RemoteDocument, RemoteImportResult, RemoteLinkConfig, RemoteWorkspaceSummary } from "./types.js";
2
- export declare const REMOTE_CONFIG_VERSION = 1;
1
+ import type { RemoteActorIdentity, RemoteDocument, RemoteImportResult, RemoteLinkConfig, RemoteTarget, RemoteWorkspaceSummary } from "./types.js";
2
+ export declare const REMOTE_CONFIG_VERSION = 4;
3
3
  export declare const REMOTE_PROVIDER = "paseo";
4
+ export declare const REMOTE_BACKEND_ACTOR_TYPE = "reffyRemoteBackend";
5
+ export declare const REMOTE_BACKEND_VERSION = "v2";
6
+ export declare const REMOTE_MANAGER_ACTOR_TYPE = "reffyWorkspaceManager";
7
+ export declare const REMOTE_MANAGER_VERSION = "v1";
4
8
  export interface WorkspaceIdentity {
5
9
  project_id: string;
6
- workspace_name: string;
10
+ workspace_id: string;
7
11
  }
8
- export interface EnsureRemoteInitOptions {
9
- endpoint: string;
10
- podName?: string;
11
- actorId?: string;
12
- provision: boolean;
13
- identity: WorkspaceIdentity;
12
+ export interface ManifestIdentityLike {
13
+ project_id?: string;
14
+ workspace_ids?: string[];
14
15
  }
15
- export interface EnsureRemoteInitResult {
16
- config: RemoteLinkConfig;
17
- created_pod: boolean;
18
- created_actor: boolean;
16
+ export interface ResolvedRemoteTarget {
17
+ endpoint: string;
18
+ manager: RemoteActorIdentity;
19
+ workspace_backend: RemoteActorIdentity;
20
+ last_imported_at?: string;
19
21
  }
20
22
  export interface ValidatedImportResult {
21
23
  imported: number;
@@ -24,42 +26,149 @@ export interface ValidatedImportResult {
24
26
  deleted: number;
25
27
  }
26
28
  export declare function resolveRemoteConfigPath(repoRoot: string): string;
29
+ export declare function resolveSelectedWorkspaceId(identity: ManifestIdentityLike, override: string | undefined): string;
30
+ export declare function requireWorkspaceIdentity(identity: ManifestIdentityLike, selectedWorkspaceId: string): WorkspaceIdentity;
27
31
  export declare function readRemoteConfig(repoRoot: string): Promise<RemoteLinkConfig | null>;
28
32
  export declare function writeRemoteConfig(repoRoot: string, config: RemoteLinkConfig): Promise<string>;
29
- export declare function updateRemoteConfigMetadata(repoRoot: string, patch: Partial<Pick<RemoteLinkConfig, "last_imported_at">>): Promise<RemoteLinkConfig | null>;
30
- export declare function mergeRemoteConfig(stored: RemoteLinkConfig | null, overrides: Partial<Pick<RemoteLinkConfig, "endpoint" | "pod_name" | "actor_id">>): RemoteLinkConfig | null;
31
- export declare function describeRemoteLinkage(config: Pick<RemoteLinkConfig, "endpoint" | "pod_name" | "actor_id">): string;
32
- export declare class PaseoRemoteClient {
33
- private readonly config;
34
- constructor(config: Pick<RemoteLinkConfig, "endpoint" | "pod_name" | "actor_id">);
33
+ export declare function updateRemoteConfigMetadata(repoRoot: string, workspaceId: string, patch: Partial<Pick<RemoteTarget, "last_imported_at">>): Promise<RemoteLinkConfig | null>;
34
+ export declare function removeWorkspaceTarget(repoRoot: string, existing: RemoteLinkConfig, workspaceId: string): Promise<{
35
+ config: RemoteLinkConfig;
36
+ existed: boolean;
37
+ }>;
38
+ export declare function upsertWorkspaceTarget(repoRoot: string, existing: RemoteLinkConfig, workspaceId: string, workspaceBackend: RemoteActorIdentity): Promise<RemoteLinkConfig>;
39
+ export declare function resolveRemoteTarget(stored: RemoteLinkConfig | null, workspaceId: string, endpoint: string): ResolvedRemoteTarget | null;
40
+ export declare function describeRemoteLinkage(linkage: {
41
+ endpoint: string;
42
+ manager: RemoteActorIdentity;
43
+ workspace_backend: RemoteActorIdentity;
44
+ workspace_id: string;
45
+ }): string;
46
+ export declare class RemoteHttpError extends Error {
47
+ readonly status: number;
48
+ readonly body: string;
49
+ readonly url: string;
50
+ readonly method: string;
51
+ constructor(status: number, statusText: string, body: string, url: string, method: string);
52
+ }
53
+ export interface ManagerWorkspaceResponse {
54
+ workspace?: {
55
+ workspace_id?: string;
56
+ backend?: {
57
+ pod_name?: string;
58
+ actor_id?: string;
59
+ };
60
+ metadata?: Record<string, unknown>;
61
+ };
62
+ }
63
+ export interface ManagerProjectListResponse {
64
+ projects?: Array<{
65
+ project_id?: string;
66
+ metadata?: Record<string, unknown>;
67
+ }>;
68
+ }
69
+ export interface ManagerProjectResponse {
70
+ project?: {
71
+ project_id?: string;
72
+ metadata?: Record<string, unknown>;
73
+ };
74
+ }
75
+ export interface CreateManagerActorResult {
76
+ actorId: string;
77
+ managerAuthToken: string;
78
+ }
79
+ export declare class PaseoManagerClient {
80
+ private readonly endpoint;
81
+ private readonly identity;
82
+ private readonly token?;
83
+ constructor(endpoint: string, identity: RemoteActorIdentity, token?: string);
84
+ private base;
85
+ private requireToken;
35
86
  createPod(): Promise<string>;
36
- createActor(identity: WorkspaceIdentity): Promise<string>;
37
- getWorkspace(): Promise<RemoteWorkspaceSummary>;
38
- importWorkspace(documents: RemoteDocument[], replaceMissing?: boolean): Promise<RemoteImportResult>;
39
- getSnapshot(prefix?: string): Promise<{
40
- documents?: Array<{
41
- path?: string;
42
- }>;
87
+ createManagerActor(podName: string): Promise<CreateManagerActorResult>;
88
+ createWorkspace(workspaceId: string, metadata?: Record<string, unknown>): Promise<RemoteActorIdentity>;
89
+ getWorkspace(workspaceId: string): Promise<RemoteActorIdentity>;
90
+ registerProject(workspaceId: string, projectId: string): Promise<{
91
+ alreadyRegistered: boolean;
43
92
  }>;
44
- getDocument(documentPath: string): Promise<{
45
- document?: {
46
- path?: string;
47
- content?: string;
48
- content_type?: string;
49
- };
93
+ listProjects(workspaceId: string): Promise<ManagerProjectListResponse>;
94
+ getProject(workspaceId: string, projectId: string): Promise<ManagerProjectResponse>;
95
+ deleteWorkspace(workspaceId: string): Promise<{
96
+ alreadyAbsent: boolean;
50
97
  }>;
51
98
  }
52
- export declare function ensureRemoteInit(repoRoot: string, options: EnsureRemoteInitOptions): Promise<EnsureRemoteInitResult>;
53
- export declare function requireWorkspaceIdentity(identity: {
54
- project_id?: string;
55
- workspace_name?: string;
56
- }): WorkspaceIdentity;
57
- export declare function extractWorkspaceIdentity(summary: RemoteWorkspaceSummary): {
58
- project_id?: string | null;
59
- workspace_name?: string | null;
99
+ export interface ProjectDocumentListResponse {
100
+ documents?: Array<{
101
+ path?: string;
102
+ content?: string;
103
+ content_type?: string;
104
+ metadata?: Record<string, unknown>;
105
+ }>;
106
+ }
107
+ export interface ProjectDocumentResponse {
108
+ document?: {
109
+ path?: string;
110
+ content?: string;
111
+ content_type?: string;
112
+ metadata?: Record<string, unknown>;
113
+ };
114
+ }
115
+ export interface ProjectSnapshotResponse {
116
+ snapshot?: Record<string, unknown>;
117
+ documents?: Array<{
118
+ path?: string;
119
+ }>;
120
+ }
121
+ export declare class PaseoWorkspaceBackendClient {
122
+ private readonly endpoint;
123
+ private readonly identity;
124
+ private readonly token;
125
+ constructor(endpoint: string, identity: RemoteActorIdentity, token: string);
126
+ private base;
127
+ getWorkspace(): Promise<RemoteWorkspaceSummary>;
128
+ importProject(projectId: string, documents: RemoteDocument[], replaceMissing?: boolean): Promise<RemoteImportResult>;
129
+ listProjectDocuments(projectId: string, options?: {
130
+ prefix?: string;
131
+ }): Promise<ProjectDocumentListResponse>;
132
+ getProjectDocument(projectId: string, documentPath: string): Promise<ProjectDocumentResponse>;
133
+ getProjectSnapshot(projectId: string): Promise<ProjectSnapshotResponse>;
134
+ }
135
+ export interface EnsureManagerInitOptions {
136
+ endpoint: string;
137
+ managerPodName?: string;
138
+ managerActorId?: string;
139
+ provision: boolean;
140
+ existingConfig: RemoteLinkConfig | null;
141
+ }
142
+ export interface EnsureManagerInitResult {
143
+ config: RemoteLinkConfig;
144
+ created_pod: boolean;
145
+ created_actor: boolean;
146
+ /** Bearer token returned by the backend when a fresh manager actor was created. Only set when `created_actor` is true. */
147
+ manager_auth_token?: string;
148
+ }
149
+ export declare function ensureManagerInit(repoRoot: string, options: EnsureManagerInitOptions): Promise<EnsureManagerInitResult>;
150
+ export type WorkspaceTargetMode = "create" | "resolve" | "auto";
151
+ export interface EnsureWorkspaceTargetOptions {
152
+ workspaceId: string;
153
+ mode: WorkspaceTargetMode;
154
+ endpoint: string;
155
+ token: string;
156
+ metadata?: Record<string, unknown>;
157
+ }
158
+ export interface EnsureWorkspaceTargetResult {
159
+ config: RemoteLinkConfig;
160
+ workspace_backend: RemoteActorIdentity;
161
+ outcome: "created" | "resolved" | "reused";
162
+ }
163
+ export declare function ensureWorkspaceTarget(repoRoot: string, config: RemoteLinkConfig, options: EnsureWorkspaceTargetOptions): Promise<EnsureWorkspaceTargetResult>;
164
+ export declare function extractWorkspaceSummaryIdentity(summary: RemoteWorkspaceSummary): {
165
+ workspace_id?: string | null;
166
+ actor_type?: string | null;
167
+ backend_version?: string | null;
60
168
  document_count?: number | null;
169
+ registered_project_count?: number | null;
61
170
  };
62
- export declare function assertRemoteIdentity(summary: RemoteWorkspaceSummary, identity: WorkspaceIdentity): void;
171
+ export declare function assertWorkspaceSummaryIdentity(summary: RemoteWorkspaceSummary, expectedWorkspaceId: string): void;
63
172
  export declare function validateImportResult(result: RemoteImportResult): ValidatedImportResult;
64
173
  export declare function toCanonicalRemotePath(refsDir: string, filePath: string): string;
65
174
  export declare function collectWorkspaceDocuments(repoRoot: string): Promise<RemoteDocument[]>;