open-research-protocol 0.4.19 → 0.4.20

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.
@@ -1,6 +1,14 @@
1
1
  import process from "node:process";
2
2
 
3
- import { buildDirectCommand, buildLaunchPlan, deriveWorkspaceId, getResumeCommand, parseWorkspaceSource } from "./core-plan.js";
3
+ import {
4
+ buildCloneCommand,
5
+ buildDirectCommand,
6
+ buildLaunchPlan,
7
+ buildSetupCommand,
8
+ deriveWorkspaceId,
9
+ getResumeCommand,
10
+ parseWorkspaceSource,
11
+ } from "./core-plan.js";
4
12
  import { loadWorkspaceSource } from "./orp.js";
5
13
 
6
14
  export function parseWorkspaceTabsArgs(argv = []) {
@@ -61,6 +69,7 @@ export function buildWorkspaceTabsReport(source, parsed, options = {}) {
61
69
  sourceLabel: source.sourceLabel,
62
70
  title: parsed.manifest?.title || source.title,
63
71
  workspaceId: deriveWorkspaceId(source, parsed),
72
+ machine: parsed.manifest?.machine || null,
64
73
  parseMode: parsed.parseMode,
65
74
  tabCount: launchTabs.length,
66
75
  skippedCount: parsed.skipped.length,
@@ -68,10 +77,16 @@ export function buildWorkspaceTabsReport(source, parsed, options = {}) {
68
77
  index: index + 1,
69
78
  title: tab.title,
70
79
  path: tab.path,
80
+ remoteUrl: tab.remoteUrl || null,
81
+ remoteBranch: tab.remoteBranch || null,
82
+ bootstrapCommand: tab.bootstrapCommand || null,
71
83
  resumeCommand: getResumeCommand(tab),
72
84
  restartCommand: buildDirectCommand(
73
85
  {
74
86
  path: tab.path,
87
+ remoteUrl: tab.remoteUrl || null,
88
+ remoteBranch: tab.remoteBranch || null,
89
+ bootstrapCommand: tab.bootstrapCommand || null,
75
90
  resumeCommand: tab.resumeCommand || null,
76
91
  resumeTool: tab.resumeTool || null,
77
92
  resumeSessionId: tab.sessionId || null,
@@ -79,6 +94,8 @@ export function buildWorkspaceTabsReport(source, parsed, options = {}) {
79
94
  },
80
95
  { resume: true },
81
96
  ),
97
+ cloneCommand: buildCloneCommand(tab),
98
+ setupCommand: buildSetupCommand(tab),
82
99
  resumeTool: tab.resumeTool || null,
83
100
  resumeSessionId: tab.sessionId || null,
84
101
  codexSessionId: tab.resumeTool === "codex" ? tab.sessionId || null : null,
@@ -92,6 +109,13 @@ export function summarizeWorkspaceTabs(report) {
92
109
  const lines = [
93
110
  `Source: ${report.sourceLabel}`,
94
111
  `Workspace ID: ${report.workspaceId}`,
112
+ ...(report.machine?.machineLabel
113
+ ? [
114
+ `Machine: ${report.machine.machineLabel}${
115
+ report.machine.platform ? ` (${report.machine.platform})` : ""
116
+ }${report.machine.machineId ? ` [${report.machine.machineId}]` : ""}`,
117
+ ]
118
+ : []),
95
119
  `Saved tabs: ${report.tabCount}`,
96
120
  `Parse mode: ${report.parseMode}`,
97
121
  "",
@@ -100,6 +124,18 @@ export function summarizeWorkspaceTabs(report) {
100
124
  for (const tab of report.tabs) {
101
125
  lines.push(`${String(tab.index).padStart(2, "0")}. ${tab.title}`);
102
126
  lines.push(` path: ${tab.path}`);
127
+ if (tab.remoteUrl) {
128
+ lines.push(` remote: ${tab.remoteUrl}${tab.remoteBranch ? ` [branch ${tab.remoteBranch}]` : ""}`);
129
+ }
130
+ if (tab.cloneCommand) {
131
+ lines.push(` clone: ${tab.cloneCommand}`);
132
+ }
133
+ if (tab.bootstrapCommand) {
134
+ lines.push(` bootstrap: ${tab.bootstrapCommand}`);
135
+ }
136
+ if (tab.setupCommand) {
137
+ lines.push(` setup: ${tab.setupCommand}`);
138
+ }
103
139
  if (tab.resumeCommand) {
104
140
  lines.push(` resume: ${tab.restartCommand}`);
105
141
  }
@@ -135,8 +171,9 @@ Options:
135
171
  -h, --help Show this help text
136
172
 
137
173
  Notes:
138
- - This shows the saved tab order plus any stored \`codex resume ...\` or \`claude --resume ...\` command metadata.
174
+ - This shows the saved tab order plus any stored local path, remote repo, bootstrap command, and \`codex resume ...\` / \`claude --resume ...\` metadata.
139
175
  - The human-readable \`resume:\` line is already copyable and includes the saved \`cd ... && resume ...\` recovery command.
176
+ - When a tab also has \`remote:\` or \`setup:\` lines, those are the portable cross-machine clues for cloning and preparing the repo on another rig.
140
177
  - The selector can be \`main\`, \`offhand\`, a hosted idea id, a hosted workspace id, a local workspace id, or a saved workspace title/slug.
141
178
  `);
142
179
  }
@@ -118,14 +118,23 @@ test("normalizeWorkspaceManifest keeps ledger fields and strips nothing importan
118
118
  version: "1",
119
119
  workspaceId: "terminal-paths",
120
120
  title: "Terminal Paths",
121
+ machine: {
122
+ machineId: "mac-studio:darwin",
123
+ machineLabel: "Mac Studio",
124
+ platform: "darwin",
125
+ },
121
126
  tabs: [
122
127
  {
123
128
  title: "orp",
124
129
  path: "/Volumes/Code_2TB/code/orp",
130
+ remoteUrl: "git@github.com:SproutSeeds/orp.git",
131
+ bootstrapCommand: "npm install",
125
132
  },
126
133
  {
127
134
  title: "lab",
128
135
  path: "/Volumes/Code_2TB/code/anthropic-lab",
136
+ remoteUrl: "git@github.com:anthropic/lab.git",
137
+ remoteBranch: "main",
129
138
  resumeTool: "claude",
130
139
  resumeSessionId: "claude-456",
131
140
  },
@@ -134,8 +143,12 @@ test("normalizeWorkspaceManifest keeps ledger fields and strips nothing importan
134
143
 
135
144
  assert.equal(manifest.workspaceId, "terminal-paths");
136
145
  assert.equal(manifest.title, "Terminal Paths");
146
+ assert.equal(manifest.machine?.machineLabel, "Mac Studio");
137
147
  assert.equal(manifest.tabs[0]?.title, "orp");
148
+ assert.equal(manifest.tabs[0]?.remoteUrl, "git@github.com:SproutSeeds/orp.git");
149
+ assert.equal(manifest.tabs[0]?.bootstrapCommand, "npm install");
138
150
  assert.equal(manifest.tabs[1]?.resumeTool, "claude");
151
+ assert.equal(manifest.tabs[1]?.remoteBranch, "main");
139
152
  });
140
153
 
141
154
  test("extractWorkspaceNarrativeNotes removes structured workspace blocks and legacy path lines", () => {
@@ -83,6 +83,10 @@ test("parseWorkspaceAddTabArgs accepts explicit resume metadata", () => {
83
83
  "main",
84
84
  "--path",
85
85
  "/Volumes/Code_2TB/code/new-project",
86
+ "--remote-url",
87
+ "git@github.com:org/new-project.git",
88
+ "--bootstrap-command",
89
+ "npm install",
86
90
  "--resume-tool",
87
91
  "claude",
88
92
  "--resume-session-id",
@@ -92,6 +96,8 @@ test("parseWorkspaceAddTabArgs accepts explicit resume metadata", () => {
92
96
 
93
97
  assert.equal(parsed.ideaId, "main");
94
98
  assert.equal(parsed.path, "/Volumes/Code_2TB/code/new-project");
99
+ assert.equal(parsed.remoteUrl, "git@github.com:org/new-project.git");
100
+ assert.equal(parsed.bootstrapCommand, "npm install");
95
101
  assert.equal(parsed.resumeTool, "claude");
96
102
  assert.equal(parsed.resumeSessionId, "claude-456");
97
103
  assert.equal(parsed.json, true);
@@ -128,8 +134,12 @@ test("parseWorkspaceCreateArgs validates slug titles and optional seed metadata"
128
134
  "main-cody-1",
129
135
  "--slot",
130
136
  "main",
137
+ "--machine-label",
138
+ "Mac Studio",
131
139
  "--path",
132
140
  "/Volumes/Code_2TB/code/orp",
141
+ "--remote-url",
142
+ "git@github.com:SproutSeeds/orp.git",
133
143
  "--resume-tool",
134
144
  "claude",
135
145
  "--resume-session-id",
@@ -139,7 +149,9 @@ test("parseWorkspaceCreateArgs validates slug titles and optional seed metadata"
139
149
 
140
150
  assert.equal(parsed.title, "main-cody-1");
141
151
  assert.equal(parsed.slotName, "main");
152
+ assert.equal(parsed.machineLabel, "Mac Studio");
142
153
  assert.equal(parsed.path, "/Volumes/Code_2TB/code/orp");
154
+ assert.equal(parsed.remoteUrl, "git@github.com:SproutSeeds/orp.git");
143
155
  assert.equal(parsed.resumeTool, "claude");
144
156
  assert.equal(parsed.resumeSessionId, "claude-456");
145
157
  assert.equal(parsed.json, true);
@@ -157,6 +169,8 @@ test("addTabToManifest canonicalizes Claude resume commands from tool plus sessi
157
169
  const result = addTabToManifest(sampleManifest(), {
158
170
  path: "/Volumes/Code_2TB/code/anthropic-lab",
159
171
  title: "anthropic-lab",
172
+ remoteUrl: "git@github.com:anthropic/anthropic-lab.git",
173
+ bootstrapCommand: "uv sync",
160
174
  resumeTool: "claude",
161
175
  resumeSessionId: "claude-456",
162
176
  });
@@ -165,6 +179,8 @@ test("addTabToManifest canonicalizes Claude resume commands from tool plus sessi
165
179
  assert.equal(result.manifest.tabs.length, 3);
166
180
  assert.equal(result.manifest.tabs[2]?.path, "/Volumes/Code_2TB/code/anthropic-lab");
167
181
  assert.equal(result.manifest.tabs[2]?.title, "anthropic-lab");
182
+ assert.equal(result.manifest.tabs[2]?.remoteUrl, "git@github.com:anthropic/anthropic-lab.git");
183
+ assert.equal(result.manifest.tabs[2]?.bootstrapCommand, "uv sync");
168
184
  assert.equal(result.manifest.tabs[2]?.resumeCommand, "claude --resume claude-456");
169
185
  assert.equal(result.manifest.tabs[2]?.resumeTool, "claude");
170
186
  assert.equal(result.manifest.tabs[2]?.sessionId, "claude-456");
@@ -250,6 +266,10 @@ test("runWorkspaceAddTab updates a local workspace manifest file", async () => {
250
266
  manifestPath,
251
267
  "--path",
252
268
  "/Volumes/Code_2TB/code/anthropic-lab",
269
+ "--remote-url",
270
+ "git@github.com:anthropic/anthropic-lab.git",
271
+ "--bootstrap-command",
272
+ "uv sync",
253
273
  "--resume-tool",
254
274
  "claude",
255
275
  "--resume-session-id",
@@ -263,8 +283,11 @@ test("runWorkspaceAddTab updates a local workspace manifest file", async () => {
263
283
  assert.equal(code, 0);
264
284
  assert.equal(payload.action, "add-tab");
265
285
  assert.equal(payload.tabCount, 3);
286
+ assert.equal(payload.tab.remoteUrl, "git@github.com:anthropic/anthropic-lab.git");
287
+ assert.equal(payload.tab.bootstrapCommand, "uv sync");
266
288
  assert.equal(payload.tab.resumeCommand, "claude --resume claude-456");
267
289
  assert.equal(saved.tabs.length, 3);
290
+ assert.equal(saved.tabs[2]?.remoteUrl, "git@github.com:anthropic/anthropic-lab.git");
268
291
  assert.equal(saved.tabs[2]?.resumeCommand, "claude --resume claude-456");
269
292
  });
270
293
  });
@@ -339,8 +362,14 @@ test("runWorkspaceCreate creates a local managed workspace and auto-assigns main
339
362
  const { code, stdout } = await captureStdout(() =>
340
363
  runWorkspaceCreate([
341
364
  "main-cody-1",
365
+ "--machine-label",
366
+ "Mac Studio",
342
367
  "--path",
343
368
  "/Volumes/Code_2TB/code/orp",
369
+ "--remote-url",
370
+ "git@github.com:SproutSeeds/orp.git",
371
+ "--bootstrap-command",
372
+ "npm install",
344
373
  "--resume-tool",
345
374
  "claude",
346
375
  "--resume-session-id",
@@ -357,7 +386,28 @@ test("runWorkspaceCreate creates a local managed workspace and auto-assigns main
357
386
  assert.equal(payload.workspaceTitle, "main-cody-1");
358
387
  assert.equal(payload.tabCount, 1);
359
388
  assert.equal(saved.title, "main-cody-1");
389
+ assert.equal(saved.machine.machineLabel, "Mac Studio");
390
+ assert.equal(saved.tabs[0]?.remoteUrl, "git@github.com:SproutSeeds/orp.git");
391
+ assert.equal(saved.tabs[0]?.bootstrapCommand, "npm install");
360
392
  assert.equal(saved.tabs[0]?.resumeCommand, "claude --resume claude-456");
361
393
  assert.equal(slots.slots.main.title, "main-cody-1");
362
394
  });
363
395
  });
396
+
397
+ test("runWorkspaceCreate allows an empty local ledger before any tabs are added", async () => {
398
+ await withTempConfigHome(async () => {
399
+ const { code, stdout } = await captureStdout(() =>
400
+ runWorkspaceCreate([
401
+ "empty-ledger",
402
+ "--json",
403
+ ]),
404
+ );
405
+ const payload = JSON.parse(stdout);
406
+ const saved = JSON.parse(await fs.readFile(payload.manifestPath, "utf8"));
407
+
408
+ assert.equal(code, 0);
409
+ assert.equal(payload.workspaceTitle, "empty-ledger");
410
+ assert.equal(payload.tabCount, 0);
411
+ assert.deepEqual(saved.tabs, []);
412
+ });
413
+ });
@@ -39,6 +39,11 @@ test("registerWorkspaceManifest and listTrackedWorkspaces expose tracked saved r
39
39
  version: "1",
40
40
  workspaceId: "orp-main",
41
41
  title: "ORP Main",
42
+ machine: {
43
+ machineId: "mac-studio:darwin",
44
+ machineLabel: "Mac Studio",
45
+ platform: "darwin",
46
+ },
42
47
  capture: {
43
48
  sourceApp: "iTerm",
44
49
  mode: "watch",
@@ -74,6 +79,9 @@ test("registerWorkspaceManifest and listTrackedWorkspaces expose tracked saved r
74
79
  manifestPath: path.resolve(manifestPath),
75
80
  workspaceId: "orp-main",
76
81
  title: "ORP Main",
82
+ machineId: "mac-studio:darwin",
83
+ machineLabel: "Mac Studio",
84
+ platform: "darwin",
77
85
  host: "local-macbook",
78
86
  captureMode: "watch",
79
87
  capturedAt: "2026-03-28T12:00:00.000Z",
@@ -100,6 +108,7 @@ test("registerWorkspaceManifest and listTrackedWorkspaces expose tracked saved r
100
108
  const summary = summarizeTrackedWorkspaces(result);
101
109
  assert.match(summary, /Local tracked workspaces: 1/);
102
110
  assert.match(summary, /ORP Main \[orp-main\]/);
111
+ assert.match(summary, /Machine: Mac Studio \(darwin\)/);
103
112
  assert.match(summary, /Saved resume sessions: 1/);
104
113
  assert.match(summary, /web: claude resume claude-999/);
105
114
  });
@@ -172,6 +181,9 @@ test("buildWorkspaceInventory merges hosted and local workspace state", () => {
172
181
  manifestPath: "/Users/example/.config/orp/workspaces/idea-123-deadbeef.json",
173
182
  workspaceId: "idea-123",
174
183
  title: "Main Cody 1",
184
+ machineId: "mac-studio:darwin",
185
+ machineLabel: "Mac Studio",
186
+ platform: "darwin",
175
187
  tabCount: 2,
176
188
  codexSessionCount: 1,
177
189
  updatedAt: "2026-03-30T12:00:00.000Z",
@@ -198,6 +210,11 @@ test("buildWorkspaceInventory merges hosted and local workspace state", () => {
198
210
  linkedIdea: { ideaId: "ef86" },
199
211
  metrics: { tabCount: 2 },
200
212
  state: {
213
+ capture_context: {
214
+ machine_id: "mac-studio:darwin",
215
+ machine_label: "Mac Studio",
216
+ platform: "darwin",
217
+ },
201
218
  tabs: [
202
219
  { project_root: "/Volumes/Code_2TB/code/orp", codex_session_id: "abc-123" },
203
220
  { project_root: "/Volumes/Code_2TB/code/orp-web-app" },
@@ -211,6 +228,7 @@ test("buildWorkspaceInventory merges hosted and local workspace state", () => {
211
228
 
212
229
  assert.equal(result.workspaces.length, 2);
213
230
  assert.equal(result.workspaces[0]?.workspaceId, "idea-123");
231
+ assert.equal(result.workspaces[0]?.machineLabel, "Mac Studio");
214
232
  assert.equal(result.workspaces[0]?.availability, "hosted+local");
215
233
  assert.equal(result.workspaces[0]?.syncStatus, "synced");
216
234
  assert.equal(result.workspaces[1]?.workspaceId, "local-only");
@@ -220,6 +238,7 @@ test("buildWorkspaceInventory merges hosted and local workspace state", () => {
220
238
  assert.match(summary, /Workspace inventory: 2/);
221
239
  assert.match(summary, /Hosted available: 1/);
222
240
  assert.match(summary, /Local available: 2/);
241
+ assert.match(summary, /Machine: Mac Studio \(darwin\)/);
223
242
  assert.match(summary, /Sync: synced/);
224
243
  });
225
244
 
@@ -50,11 +50,35 @@ test("buildWorkspaceTabsReport keeps duplicate titles unique and exposes generic
50
50
  sourceType: "hosted-idea",
51
51
  sourceLabel: "Workspace idea",
52
52
  title: "Workspace idea",
53
- notes: `
54
- /Volumes/Code_2TB/code/collaboration: codex resume abc-123
55
- /Volumes/Code_2TB/code/anthropic-lab: claude resume claude-456
56
- /Volumes/Code_2TB/code/collaboration
57
- `,
53
+ workspaceManifest: {
54
+ version: "1",
55
+ workspaceId: "workspace-idea",
56
+ machine: {
57
+ machineId: "mac-studio:darwin",
58
+ machineLabel: "Mac Studio",
59
+ platform: "darwin",
60
+ },
61
+ tabs: [
62
+ {
63
+ path: "/Volumes/Code_2TB/code/collaboration",
64
+ title: "collaboration",
65
+ remoteUrl: "git@github.com:org/collaboration.git",
66
+ bootstrapCommand: "npm install",
67
+ resumeCommand: "codex resume abc-123",
68
+ },
69
+ {
70
+ path: "/Volumes/Code_2TB/code/anthropic-lab",
71
+ title: "anthropic-lab",
72
+ remoteUrl: "git@github.com:anthropic/anthropic-lab.git",
73
+ remoteBranch: "main",
74
+ resumeCommand: "claude resume claude-456",
75
+ },
76
+ {
77
+ path: "/Volumes/Code_2TB/code/collaboration",
78
+ },
79
+ ],
80
+ },
81
+ notes: "",
58
82
  });
59
83
 
60
84
  const report = buildWorkspaceTabsReport(
@@ -66,9 +90,17 @@ test("buildWorkspaceTabsReport keeps duplicate titles unique and exposes generic
66
90
  parsed,
67
91
  );
68
92
 
69
- assert.match(report.workspaceId, /^workspace-/);
93
+ assert.equal(report.workspaceId, "workspace-idea");
94
+ assert.equal(report.machine?.machineLabel, "Mac Studio");
70
95
  assert.equal(report.tabCount, 3);
71
96
  assert.equal(report.tabs[0]?.title, "collaboration");
97
+ assert.equal(report.tabs[0]?.remoteUrl, "git@github.com:org/collaboration.git");
98
+ assert.equal(report.tabs[0]?.bootstrapCommand, "npm install");
99
+ assert.equal(report.tabs[0]?.cloneCommand, "git clone 'git@github.com:org/collaboration.git' 'collaboration'");
100
+ assert.equal(
101
+ report.tabs[0]?.setupCommand,
102
+ "git clone 'git@github.com:org/collaboration.git' 'collaboration' && cd 'collaboration' && npm install",
103
+ );
72
104
  assert.equal(report.tabs[0]?.resumeCommand, "codex resume abc-123");
73
105
  assert.equal(
74
106
  report.tabs[0]?.restartCommand,
@@ -77,6 +109,7 @@ test("buildWorkspaceTabsReport keeps duplicate titles unique and exposes generic
77
109
  assert.equal(report.tabs[0]?.codexSessionId, "abc-123");
78
110
  assert.equal(report.tabs[1]?.title, "anthropic-lab");
79
111
  assert.equal(report.tabs[1]?.resumeCommand, "claude resume claude-456");
112
+ assert.equal(report.tabs[1]?.remoteBranch, "main");
80
113
  assert.equal(
81
114
  report.tabs[1]?.restartCommand,
82
115
  "cd '/Volumes/Code_2TB/code/anthropic-lab' && claude resume claude-456",
@@ -96,10 +129,17 @@ test("runWorkspaceTabs prints JSON without launch commands", async () => {
96
129
  version: "1",
97
130
  workspaceId: "orp-main",
98
131
  title: "ORP Main",
132
+ machine: {
133
+ machineId: "mac-studio:darwin",
134
+ machineLabel: "Mac Studio",
135
+ platform: "darwin",
136
+ },
99
137
  tabs: [
100
138
  {
101
139
  title: "orp",
102
140
  path: "/Volumes/Code_2TB/code/orp",
141
+ remoteUrl: "git@github.com:SproutSeeds/orp.git",
142
+ bootstrapCommand: "npm install",
103
143
  resumeCommand: "claude resume claude-999",
104
144
  resumeTool: "claude",
105
145
  resumeSessionId: "claude-999",
@@ -121,8 +161,11 @@ test("runWorkspaceTabs prints JSON without launch commands", async () => {
121
161
 
122
162
  assert.equal(code, 0);
123
163
  assert.equal(parsed.workspaceId, "orp-main");
164
+ assert.equal(parsed.machine.machineLabel, "Mac Studio");
124
165
  assert.equal(parsed.tabCount, 2);
125
166
  assert.equal(parsed.tabs[0]?.title, "orp");
167
+ assert.equal(parsed.tabs[0]?.remoteUrl, "git@github.com:SproutSeeds/orp.git");
168
+ assert.equal(parsed.tabs[0]?.bootstrapCommand, "npm install");
126
169
  assert.equal(parsed.tabs[0]?.resumeCommand, "claude resume claude-999");
127
170
  assert.equal(parsed.tabs[0]?.restartCommand, "cd '/Volumes/Code_2TB/code/orp' && claude resume claude-999");
128
171
  assert.equal(parsed.tabs[0]?.claudeSessionId, "claude-999");
@@ -142,6 +185,7 @@ test("buildWorkspaceTabsReport canonicalizes Claude resume commands from tool an
142
185
  {
143
186
  title: "anthropic-lab",
144
187
  path: "/Volumes/Code_2TB/code/anthropic-lab",
188
+ remoteUrl: "git@github.com:anthropic/anthropic-lab.git",
145
189
  resumeTool: "claude",
146
190
  resumeSessionId: "claude-456",
147
191
  },
@@ -160,6 +204,7 @@ test("buildWorkspaceTabsReport canonicalizes Claude resume commands from tool an
160
204
  );
161
205
 
162
206
  assert.equal(report.tabs[0]?.resumeCommand, "claude --resume claude-456");
207
+ assert.equal(report.tabs[0]?.cloneCommand, "git clone 'git@github.com:anthropic/anthropic-lab.git' 'anthropic-lab'");
163
208
  assert.equal(
164
209
  report.tabs[0]?.restartCommand,
165
210
  "cd '/Volumes/Code_2TB/code/anthropic-lab' && claude --resume claude-456",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://orp.dev/spec/v1/workspace.schema.json",
4
4
  "title": "ORP Workspace Manifest",
5
- "description": "Structured workspace launcher manifest for ORP iTerm/tmux workspace restoration.",
5
+ "description": "Structured workspace ledger manifest for ORP local and hosted workspace recovery across machines.",
6
6
  "type": "object",
7
7
  "required": ["version", "tabs"],
8
8
  "properties": {
@@ -18,6 +18,28 @@
18
18
  "type": "string",
19
19
  "minLength": 1
20
20
  },
21
+ "machine": {
22
+ "type": "object",
23
+ "properties": {
24
+ "machineId": {
25
+ "type": "string",
26
+ "minLength": 1
27
+ },
28
+ "machineLabel": {
29
+ "type": "string",
30
+ "minLength": 1
31
+ },
32
+ "platform": {
33
+ "type": "string",
34
+ "minLength": 1
35
+ },
36
+ "host": {
37
+ "type": "string",
38
+ "minLength": 1
39
+ }
40
+ },
41
+ "additionalProperties": false
42
+ },
21
43
  "tmuxPrefix": {
22
44
  "type": "string",
23
45
  "minLength": 1
@@ -66,7 +88,7 @@
66
88
  },
67
89
  "tabs": {
68
90
  "type": "array",
69
- "minItems": 1,
91
+ "minItems": 0,
70
92
  "items": {
71
93
  "type": "object",
72
94
  "required": ["path"],
@@ -79,6 +101,18 @@
79
101
  "type": "string",
80
102
  "pattern": "^/"
81
103
  },
104
+ "remoteUrl": {
105
+ "type": "string",
106
+ "minLength": 1
107
+ },
108
+ "remoteBranch": {
109
+ "type": "string",
110
+ "minLength": 1
111
+ },
112
+ "bootstrapCommand": {
113
+ "type": "string",
114
+ "minLength": 1
115
+ },
82
116
  "resumeCommand": {
83
117
  "type": "string",
84
118
  "minLength": 1