@overaihq/cli 0.10.3 → 0.11.2

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 (3) hide show
  1. package/README.md +12 -1
  2. package/dist/index.js +95 -36
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,7 +20,7 @@ overai workspace list # find a workspace id
20
20
  overai workspace show <id> # read its name and overview
21
21
  overai workspace users <id> # list users and direct/inherited roles
22
22
  overai create agent --workspace <id> --name "Support agent"
23
- overai clone <workspaceId> # materialize resources and WORKSPACE.md
23
+ overai clone <workspaceId> # clone the workspace and its descendants
24
24
  # ...edit files...
25
25
  overai status # local edits + remote drift
26
26
  overai diff # what changed
@@ -36,6 +36,17 @@ Run `overai help` for the full command list, including function invocation,
36
36
  automation runs and per-node state, tablespace SQL (a psql-style REPL), and
37
37
  `overai dev` for live-previewing a web page from local files.
38
38
 
39
+ Clone includes accessible, non-archived descendants in nested
40
+ `workspaces/<slug>.<workspaceId>/` folders. Each workspace has its own
41
+ `.overai/manifest.json`, resource files, and `WORKSPACE.md`. Existing resource
42
+ filenames and the top-level clone directory stay unchanged.
43
+
44
+ Commands use the nearest workspace folder: `cd` into a child to edit or sync it.
45
+ `status`, `pull`, `push`, `deploy`, and `restore` still affect only that workspace,
46
+ not its descendants. Pull does not discover new child workspaces. Clone those
47
+ separately, or clone the parent into a fresh directory. Local file moves never
48
+ move resources between workspaces in OverAI.
49
+
39
50
  Inside a clone, `workspace show` and `workspace users` infer the workspace id.
40
51
  `WORKSPACE.md` is the overview captured when the clone is created; use
41
52
  `workspace show` when you need the current server value.
package/dist/index.js CHANGED
@@ -26713,12 +26713,19 @@ var getAccessToken = async () => {
26713
26713
  );
26714
26714
  deployment.pending = omit(deployment.pending, target.organizationId);
26715
26715
  writeStore(store);
26716
+ if (response?.reason === "mfa_setup_required") return response;
26716
26717
  }
26717
26718
  throw new InteractiveSignInRequired(
26718
26719
  `Org ${target.organizationId} requires browser authorization. ${signin}`
26719
26720
  );
26720
26721
  })();
26721
- if (result.type === "redirect") throw new InteractiveSignInRequired(signin);
26722
+ if (result.type === "redirect") {
26723
+ if (result.reason === "mfa_setup_required")
26724
+ throw new Error(
26725
+ `Org ${target.organizationId} requires two-factor authentication. Enroll at ${result.redirectUrl}. ${signin}`
26726
+ );
26727
+ throw new InteractiveSignInRequired(signin);
26728
+ }
26722
26729
  const claims = decodeJwtClaims(result.token);
26723
26730
  if (claims.org !== target.organizationId || target.identityId != null && claims.user_id !== target.identityId) {
26724
26731
  throw new Error(
@@ -33845,6 +33852,7 @@ import { dirname as dirname3, join as join5 } from "path";
33845
33852
  // src/commands/sync.ts
33846
33853
  import {
33847
33854
  existsSync as existsSync3,
33855
+ lstatSync,
33848
33856
  mkdirSync as mkdirSync3,
33849
33857
  readFileSync as readFileSync6,
33850
33858
  unlinkSync,
@@ -33981,43 +33989,91 @@ var runClone = async (args) => {
33981
33989
  if (existsSync3(join4(root, "WORKSPACE.md")) && partialCloneWorkspaceId == null) {
33982
33990
  throw new Error(`${root} already contains WORKSPACE.md.`);
33983
33991
  }
33984
- const resourcesByAdapter = [];
33985
- for (const adapter of allAdapters) {
33986
- resourcesByAdapter.push({
33987
- adapter,
33988
- resources: await adapter.fetchAll(workspaceId)
33989
- });
33990
- }
33991
- mkdirSync3(join4(root, ".overai"), { recursive: true });
33992
- writeFileSync4(cloneMarker, `${workspaceId}
33992
+ const pending = [{ workspace, root }];
33993
+ const seen = /* @__PURE__ */ new Set([workspace.id]);
33994
+ const plan = [];
33995
+ for (const entry of pending) {
33996
+ const marker = join4(entry.root, ".overai", "clone-in-progress");
33997
+ const completed = existsSync3(join4(entry.root, ".overai", "manifest.json"));
33998
+ if (entry.root !== root && existsSync3(entry.root)) {
33999
+ if (lstatSync(entry.root).isSymbolicLink())
34000
+ throw new Error(`Refusing to clone into symbolic link ${entry.root}.`);
34001
+ const savedId = completed ? readManifest(entry.root).workspaceId : existsSync3(marker) ? readFileSync6(marker, "utf8").trim() : null;
34002
+ if (partialCloneWorkspaceId !== workspaceId || savedId !== entry.workspace.id)
34003
+ throw new Error(`${entry.root} already exists. Choose another clone directory.`);
34004
+ }
34005
+ if (existsSync3(join4(entry.root, "workspaces")) && lstatSync(join4(entry.root, "workspaces")).isSymbolicLink())
34006
+ throw new Error(`Refusing to clone through symbolic link ${join4(entry.root, "workspaces")}.`);
34007
+ if (!completed) {
34008
+ const resourcesByAdapter = await Promise.all(allAdapters.map(async (adapter) => ({
34009
+ adapter,
34010
+ resources: await adapter.fetchAll(entry.workspace.id)
34011
+ })));
34012
+ plan.push({
34013
+ root: entry.root,
34014
+ description: entry.workspace.description,
34015
+ manifest: {
34016
+ workspaceId: entry.workspace.id,
34017
+ workspaceName: entry.workspace.name,
34018
+ organizationId: entry.workspace.organizationId,
34019
+ resources: {}
34020
+ },
34021
+ resourcesByAdapter
34022
+ });
34023
+ }
34024
+ let after = null;
34025
+ do {
34026
+ const data = await gqlRequest(
34027
+ `query CliCloneChildren($id: String!, $after: Cursor) {
34028
+ workspaceWorkspaces(condition: { parentWorkspaceId: $id }, first: 100, after: $after) {
34029
+ nodes { workspace { id name description organizationId isArchived } }
34030
+ pageInfo { hasNextPage endCursor }
34031
+ }
34032
+ }`,
34033
+ { id: entry.workspace.id, after }
34034
+ );
34035
+ for (const node of data.workspaceWorkspaces.nodes) {
34036
+ const child = node?.workspace;
34037
+ if (child == null || child.isArchived) continue;
34038
+ if (child.organizationId !== workspace.organizationId || seen.has(child.id))
34039
+ throw new Error(`Invalid workspace hierarchy at ${child.id}.`);
34040
+ if (!/^[a-zA-Z0-9_-]+$/.test(child.id))
34041
+ throw new Error(`Invalid workspace id ${child.id}.`);
34042
+ seen.add(child.id);
34043
+ pending.push({ workspace: child, root: join4(entry.root, "workspaces", `${slugify(child.name)}.${child.id}`) });
34044
+ }
34045
+ const page = data.workspaceWorkspaces.pageInfo;
34046
+ if (page.hasNextPage && (page.endCursor == null || page.endCursor === after))
34047
+ throw new Error("Workspace pagination did not advance.");
34048
+ after = page.hasNextPage ? page.endCursor : null;
34049
+ } while (after != null);
34050
+ }
34051
+ for (const entry of plan) {
34052
+ mkdirSync3(join4(entry.root, ".overai"), { recursive: true });
34053
+ writeFileSync4(join4(entry.root, ".overai", "clone-in-progress"), `${entry.manifest.workspaceId}
33993
34054
  `);
33994
- writeFileSync4(
33995
- join4(root, "WORKSPACE.md"),
33996
- workspace.description.length === 0 ? "" : `${workspace.description.trimEnd()}
34055
+ writeFileSync4(
34056
+ join4(entry.root, "WORKSPACE.md"),
34057
+ entry.description.length === 0 ? "" : `${entry.description.trimEnd()}
33997
34058
  `
33998
- );
33999
- const manifest = {
34000
- workspaceId: workspace.id,
34001
- workspaceName: workspace.name,
34002
- organizationId: workspace.organizationId,
34003
- resources: {}
34004
- };
34005
- for (const { adapter, resources: resources2 } of resourcesByAdapter) {
34006
- for (const remote of resources2) {
34007
- for (const [relPath, content] of Object.entries(remote.files)) {
34008
- writeSynced(root, relPath, content);
34059
+ );
34060
+ for (const { adapter, resources: resources2 } of entry.resourcesByAdapter) {
34061
+ for (const remote of resources2) {
34062
+ for (const [relPath, content] of Object.entries(remote.files)) {
34063
+ writeSynced(entry.root, relPath, content);
34064
+ }
34065
+ entry.manifest.resources[remote.id] = toManifestResource(adapter.type, remote);
34009
34066
  }
34010
- manifest.resources[remote.id] = toManifestResource(adapter.type, remote);
34067
+ process.stdout.write(`${entry.manifest.workspaceName}: ${adapter.type}: ${String(resources2.length)} cloned
34068
+ `);
34011
34069
  }
34012
- process.stdout.write(
34013
- `${adapter.type}: ${String(resources2.length)} cloned
34014
- `
34015
- );
34016
34070
  }
34017
- writeManifest(root, manifest);
34018
- unlinkSync(cloneMarker);
34071
+ for (const entry of plan.toReversed()) {
34072
+ writeManifest(entry.root, entry.manifest);
34073
+ unlinkSync(join4(entry.root, ".overai", "clone-in-progress"));
34074
+ }
34019
34075
  process.stdout.write(`
34020
- Cloned "${workspace.name}" into ${root}
34076
+ Cloned "${workspace.name}" and ${String(pending.length - 1)} descendant workspaces into ${root}
34021
34077
  `);
34022
34078
  };
34023
34079
  var collectStatus = async (root, manifest) => {
@@ -35262,9 +35318,12 @@ executionMemory MB).
35262
35318
  var workflow = `# CLI editing workflow
35263
35319
 
35264
35320
  1. \`overai workspace list\` to find the id, then \`overai clone <workspaceId>\`
35265
- (or \`overai pull\` in an existing clone). Workspaces nest, and clone pulls
35266
- ONE workspace \u2014 a parent's clone does not include its children, so clone
35267
- each nested workspace you need separately.
35321
+ (or \`overai pull\` in an existing clone). Clone includes accessible,
35322
+ non-archived descendants under \`workspaces/<slug>.<workspaceId>/\`, each with
35323
+ its own manifest and WORKSPACE.md. Change into a child folder to work there.
35324
+ All sync commands stay scoped to the nearest workspace, never its children.
35325
+ Pull does not discover new children; clone those separately. Moving local
35326
+ files does not move resources between workspaces in OverAI.
35268
35327
  2. Edit files. The id in each filename is authoritative; slugs are cosmetic.
35269
35328
  3. \`overai status\` / \`overai diff\` \u2014 review what changed.
35270
35329
  4. \`overai review\` \u2014 local checks, authenticated TypeScript lint against the
@@ -36230,7 +36289,7 @@ Usage:
36230
36289
  overai sql credential rotate|revoke <credentialId>
36231
36290
 
36232
36291
  Workspace sync (edit resources as local files):
36233
- overai clone <workspaceId> [dir] Materialize resources and WORKSPACE.md
36292
+ overai clone <workspaceId> [dir] Clone workspace and accessible descendants
36234
36293
  overai status Local edits + remote drift
36235
36294
  overai diff [filter] [--from <ref>] [--to <ref>]
36236
36295
  Diff versions (refs: N, base, local, deployed, latest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overaihq/cli",
3
- "version": "0.10.3",
3
+ "version": "0.11.2",
4
4
  "description": "Create, edit, deploy, and run OverAI workspace resources from the command line",
5
5
  "type": "module",
6
6
  "homepage": "https://overai.com",