@stream44.studio/t44 0.4.0-rc.24

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 (99) hide show
  1. package/.dco-signatures +9 -0
  2. package/.github/workflows/dco.yaml +12 -0
  3. package/.github/workflows/gordian-open-integrity.yaml +13 -0
  4. package/.github/workflows/test.yaml +31 -0
  5. package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
  6. package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
  7. package/.o/GordianOpenIntegrity.yaml +21 -0
  8. package/.o/assets/Hero-Terminal44-v0.jpeg +0 -0
  9. package/.o/stream44.studio/assets/Icon-v1.svg +1170 -0
  10. package/.repo-identifier +1 -0
  11. package/DCO.md +34 -0
  12. package/LICENSE.txt +186 -0
  13. package/README.md +189 -0
  14. package/bin/activate +36 -0
  15. package/bin/activate.ts +30 -0
  16. package/bin/postinstall.sh +19 -0
  17. package/bin/shell +27 -0
  18. package/bin/t44 +27 -0
  19. package/caps/ConfigSchemaStruct.ts +55 -0
  20. package/caps/Home.ts +57 -0
  21. package/caps/HomeRegistry.ts +319 -0
  22. package/caps/HomeRegistryFile.ts +144 -0
  23. package/caps/JsonSchemas.ts +220 -0
  24. package/caps/OpenApiSchema.ts +67 -0
  25. package/caps/PackageDescriptor.ts +88 -0
  26. package/caps/ProjectCatalogs.ts +153 -0
  27. package/caps/ProjectDeployment.ts +426 -0
  28. package/caps/ProjectDevelopment.ts +257 -0
  29. package/caps/ProjectPublishing.ts +654 -0
  30. package/caps/ProjectPulling.ts +234 -0
  31. package/caps/ProjectRack.ts +155 -0
  32. package/caps/ProjectRepository.ts +332 -0
  33. package/caps/ProjectTest.ts +251 -0
  34. package/caps/ProjectTestLib.ts +257 -0
  35. package/caps/RootKey.ts +219 -0
  36. package/caps/SigningKey.ts +243 -0
  37. package/caps/TaskWorkflow.ts +192 -0
  38. package/caps/WorkspaceCli.ts +448 -0
  39. package/caps/WorkspaceConfig.ts +268 -0
  40. package/caps/WorkspaceConfig.yaml +87 -0
  41. package/caps/WorkspaceConfigFile.ts +902 -0
  42. package/caps/WorkspaceConnection.ts +329 -0
  43. package/caps/WorkspaceEntityConfig.ts +78 -0
  44. package/caps/WorkspaceEntityConfig.v0.ts +77 -0
  45. package/caps/WorkspaceEntityFact.ts +218 -0
  46. package/caps/WorkspaceInfo.ts +619 -0
  47. package/caps/WorkspaceInit.ts +30 -0
  48. package/caps/WorkspaceKey.ts +338 -0
  49. package/caps/WorkspaceModel.ts +373 -0
  50. package/caps/WorkspaceProjects.ts +636 -0
  51. package/caps/WorkspacePrompt.ts +430 -0
  52. package/caps/WorkspaceShell.sh +39 -0
  53. package/caps/WorkspaceShell.ts +104 -0
  54. package/caps/WorkspaceShell.yaml +64 -0
  55. package/caps/WorkspaceShellCli.ts +109 -0
  56. package/caps/patterns/README.md +2 -0
  57. package/caps/patterns/git-scm.com/ProjectPublishing.ts +507 -0
  58. package/caps/patterns/semver.org/ProjectPublishing.ts +458 -0
  59. package/docs/Overview.drawio +248 -0
  60. package/docs/Overview.svg +4 -0
  61. package/examples/01-Lifecycle/main.test.ts +223 -0
  62. package/lib/crypto.ts +53 -0
  63. package/lib/key.ts +381 -0
  64. package/lib/schema-console-renderer.ts +181 -0
  65. package/lib/schema-resolver.ts +349 -0
  66. package/lib/ucan.ts +137 -0
  67. package/package.json +91 -0
  68. package/standalone-rt.test.ts +150 -0
  69. package/standalone-rt.ts +140 -0
  70. package/structs/HomeRegistry.ts +55 -0
  71. package/structs/HomeRegistryConfig.ts +60 -0
  72. package/structs/ProjectCatalogsConfig.ts +53 -0
  73. package/structs/ProjectDeploymentConfig.ts +56 -0
  74. package/structs/ProjectDeploymentFact.ts +106 -0
  75. package/structs/ProjectPublishingConfig.ts +78 -0
  76. package/structs/ProjectPublishingFact.ts +68 -0
  77. package/structs/ProjectPullingConfig.ts +52 -0
  78. package/structs/ProjectRack.ts +51 -0
  79. package/structs/ProjectRackConfig.ts +56 -0
  80. package/structs/RepositoryOriginDescriptor.ts +51 -0
  81. package/structs/RootKeyConfig.ts +64 -0
  82. package/structs/SigningKeyConfig.ts +64 -0
  83. package/structs/Workspace.ts +56 -0
  84. package/structs/WorkspaceCatalogs.ts +56 -0
  85. package/structs/WorkspaceCliConfig.ts +53 -0
  86. package/structs/WorkspaceConfig.ts +64 -0
  87. package/structs/WorkspaceConfigFile.ts +50 -0
  88. package/structs/WorkspaceConfigFileMeta.ts +70 -0
  89. package/structs/WorkspaceKey.ts +55 -0
  90. package/structs/WorkspaceKeyConfig.ts +56 -0
  91. package/structs/WorkspaceMappingsConfig.ts +56 -0
  92. package/structs/WorkspaceProject.ts +104 -0
  93. package/structs/WorkspaceProjectsConfig.ts +67 -0
  94. package/structs/WorkspaceShellConfig.ts +83 -0
  95. package/structs/patterns/README.md +2 -0
  96. package/structs/patterns/git-scm.com/ProjectPublishingFact.ts +46 -0
  97. package/tsconfig.json +33 -0
  98. package/workspace-rt.ts +152 -0
  99. package/workspace.yaml +3 -0
@@ -0,0 +1,430 @@
1
+
2
+ // Error prefix for missing credentials in non-interactive mode
3
+ // Format: "MISSING_CREDENTIALS:provider:credentialName:message"
4
+ export const MISSING_CREDENTIALS_PREFIX = 'MISSING_CREDENTIALS:'
5
+
6
+ export function isMissingCredentialsError(error: any): { provider: string; credentialName: string; message: string } | null {
7
+ if (error?.message?.startsWith(MISSING_CREDENTIALS_PREFIX)) {
8
+ const parts = error.message.slice(MISSING_CREDENTIALS_PREFIX.length).split(':')
9
+ if (parts.length >= 3) {
10
+ return {
11
+ provider: parts[0],
12
+ credentialName: parts[1],
13
+ message: parts.slice(2).join(':')
14
+ }
15
+ }
16
+ }
17
+ return null
18
+ }
19
+
20
+ // Global prompt queue to ensure sequential prompting and deduplication
21
+ const globalPromptQueue: Array<() => Promise<any>> = []
22
+ const globalActivePrompts: Record<string, Promise<any>> = {}
23
+ const globalShownTitles: Set<string> = new Set()
24
+ let globalIsProcessing = false
25
+
26
+ async function processGlobalQueue() {
27
+ if (globalIsProcessing || globalPromptQueue.length === 0) return
28
+ globalIsProcessing = true
29
+
30
+ while (globalPromptQueue.length > 0) {
31
+ const task = globalPromptQueue.shift()!
32
+ await task()
33
+ }
34
+
35
+ globalIsProcessing = false
36
+ }
37
+
38
+ export async function capsule({
39
+ encapsulate,
40
+ CapsulePropertyTypes,
41
+ makeImportStack
42
+ }: {
43
+ encapsulate: any
44
+ CapsulePropertyTypes: any
45
+ makeImportStack: any
46
+ }) {
47
+ return encapsulate({
48
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
49
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
50
+ '#': {
51
+ WorkspaceCli: {
52
+ type: CapsulePropertyTypes.Mapping,
53
+ value: '@stream44.studio/t44/caps/WorkspaceCli'
54
+ },
55
+ WorkspaceTest: {
56
+ type: CapsulePropertyTypes.Mapping,
57
+ value: '@stream44.studio/t44/caps/ProjectTest'
58
+ },
59
+ prompt: {
60
+ type: CapsulePropertyTypes.Function,
61
+ value: async function (this: any, questions: any[]): Promise<any> {
62
+ // Check if --yes flag is set
63
+ const cliOptions = await this.WorkspaceCli.cliOptions
64
+ const yes = cliOptions?.yes || false
65
+
66
+ // If --yes is set, return default values
67
+ if (yes) {
68
+ const result: Record<string, any> = {}
69
+ for (const question of questions) {
70
+ result[question.name] = question.default
71
+ }
72
+ return result
73
+ }
74
+
75
+ // Otherwise, load inquirer and prompt user
76
+ const inquirer = await import('inquirer')
77
+ try {
78
+ return await inquirer.default.prompt(questions)
79
+ } catch (error: any) {
80
+ if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
81
+ const chalk = (await import('chalk')).default
82
+ console.log(chalk.red('\n\nABORTED\n'))
83
+ process.exit(0)
84
+ }
85
+ throw error
86
+ }
87
+ }
88
+ },
89
+ confirm: {
90
+ type: CapsulePropertyTypes.Function,
91
+ value: async function (this: any, {
92
+ title,
93
+ description,
94
+ message,
95
+ defaultValue = false,
96
+ onSuccess,
97
+ onAbort
98
+ }: {
99
+ title?: string
100
+ description?: string | string[]
101
+ message: string
102
+ defaultValue?: boolean
103
+ onSuccess?: (confirmed: boolean) => Promise<void> | void
104
+ onAbort?: () => Promise<void> | void
105
+ }): Promise<boolean> {
106
+ const chalk = (await import('chalk')).default
107
+
108
+ if (title) {
109
+ console.log(chalk.cyan(`\n${title}\n`))
110
+ }
111
+ if (description) {
112
+ const lines = Array.isArray(description) ? description : [description]
113
+ for (const line of lines) {
114
+ console.log(chalk.gray(` ${line}`))
115
+ }
116
+ console.log('')
117
+ }
118
+
119
+ // Check if --yes flag is set
120
+ const cliOptions = await this.WorkspaceCli.cliOptions
121
+ const yes = cliOptions?.yes || false
122
+
123
+ let confirmed: boolean
124
+
125
+ // If --yes is set, return default value
126
+ if (yes) {
127
+ confirmed = defaultValue
128
+ } else {
129
+ try {
130
+ // Otherwise, load inquirer and prompt user
131
+ const inquirer = await import('inquirer')
132
+ const result = await inquirer.default.prompt([
133
+ {
134
+ type: 'confirm',
135
+ name: 'confirmed',
136
+ message,
137
+ default: defaultValue
138
+ }
139
+ ])
140
+ confirmed = result.confirmed
141
+ } catch (error: any) {
142
+ if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
143
+ console.log(chalk.red('\n\nABORTED\n'))
144
+ if (onAbort) {
145
+ await onAbort()
146
+ }
147
+ process.exit(0)
148
+ }
149
+ throw error
150
+ }
151
+ }
152
+
153
+ if (onSuccess) {
154
+ await onSuccess(confirmed)
155
+ }
156
+
157
+ return confirmed
158
+ }
159
+ },
160
+ input: {
161
+ type: CapsulePropertyTypes.Function,
162
+ value: async function (this: any, {
163
+ message,
164
+ defaultValue,
165
+ validate,
166
+ promptFactId
167
+ }: {
168
+ message: string
169
+ defaultValue?: string
170
+ validate?: (input: string) => boolean | string
171
+ promptFactId?: string
172
+ }): Promise<string> {
173
+ // Try to get value from env first (works in both interactive and non-interactive modes)
174
+ if (promptFactId) {
175
+ const envValue = this.getEnvValueForFactReference(promptFactId)
176
+ if (envValue) {
177
+ return envValue
178
+ }
179
+ }
180
+
181
+ // In non-interactive mode, throw error with MISSING_CREDENTIALS prefix
182
+ if (!process.stdin.isTTY && promptFactId) {
183
+ // Extract provider from promptFactId
184
+ const providerMatch = promptFactId.match(/patterns\/([^/]+)\//)
185
+ const provider = providerMatch ? providerMatch[1] : 'unknown'
186
+ const credentialName = message.replace(/:$/, '')
187
+
188
+ throw new Error(
189
+ `MISSING_CREDENTIALS:${provider}:${credentialName}:` +
190
+ `Cannot prompt for "${message}" in non-interactive mode. ` +
191
+ `Please run interactively first to configure credentials, or set the corresponding env variable.`
192
+ )
193
+ }
194
+
195
+ // Check for existing prompt with same ID
196
+ if (promptFactId && promptFactId in globalActivePrompts) {
197
+ return globalActivePrompts[promptFactId]
198
+ }
199
+
200
+ // Create the prompt task
201
+ const promptTask = async () => {
202
+ try {
203
+ // Check if --yes flag is set
204
+ const cliOptions = await this.WorkspaceCli.cliOptions
205
+ const yes = cliOptions?.yes || false
206
+
207
+ // If --yes is set, return default value
208
+ if (yes) {
209
+ if (!defaultValue) {
210
+ throw new Error(`Cannot use --yes flag without a default value for prompt: ${message}`)
211
+ }
212
+ return defaultValue
213
+ }
214
+
215
+ // Otherwise, load inquirer and prompt user
216
+ const inquirer = await import('inquirer')
217
+ const question: any = {
218
+ type: 'input',
219
+ name: 'value',
220
+ message,
221
+ validate
222
+ }
223
+
224
+ // Only add default if explicitly provided
225
+ if (defaultValue !== undefined) {
226
+ question.default = defaultValue
227
+ }
228
+
229
+ const { value } = await inquirer.default.prompt([question])
230
+ return value
231
+ } catch (error: any) {
232
+ if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
233
+ const chalk = (await import('chalk')).default
234
+ console.log(chalk.red('\n\nABORTED\n'))
235
+ process.exit(0)
236
+ }
237
+ throw error
238
+ } finally {
239
+ // Remove from active prompts when done
240
+ if (promptFactId) {
241
+ delete globalActivePrompts[promptFactId]
242
+ }
243
+ }
244
+ }
245
+
246
+ // Create promise and add to queue
247
+ const promise = new Promise<string>((resolve, reject) => {
248
+ globalPromptQueue.push(async () => {
249
+ try {
250
+ const result = await promptTask()
251
+ resolve(result)
252
+ } catch (error) {
253
+ reject(error)
254
+ }
255
+ })
256
+ })
257
+
258
+ // Track active prompt
259
+ if (promptFactId) {
260
+ globalActivePrompts[promptFactId] = promise
261
+ }
262
+
263
+ // Start processing queue (inlined to avoid ambient reference issues)
264
+ if (!globalIsProcessing && globalPromptQueue.length > 0) {
265
+ globalIsProcessing = true
266
+
267
+ // Process queue asynchronously
268
+ ; (async () => {
269
+ while (globalPromptQueue.length > 0) {
270
+ const task = globalPromptQueue.shift()!
271
+ await task()
272
+ }
273
+ globalIsProcessing = false
274
+ })()
275
+ }
276
+
277
+ return promise
278
+ }
279
+ },
280
+ getEnvValueForFactReference: {
281
+ type: CapsulePropertyTypes.Function,
282
+ value: function (this: any, factReference: string): string | undefined {
283
+ const workspaceTest = this.WorkspaceTest
284
+ if (!workspaceTest) return undefined
285
+
286
+ const envConfig = workspaceTest.env || {}
287
+
288
+ for (const [envVarName, envDef] of Object.entries(envConfig)) {
289
+ if ((envDef as any)?.factReference === factReference) {
290
+ return workspaceTest.getEnvValue(envVarName)
291
+ }
292
+ }
293
+ return undefined
294
+ }
295
+ },
296
+ select: {
297
+ type: CapsulePropertyTypes.Function,
298
+ value: async function (this: any, {
299
+ message,
300
+ choices,
301
+ defaultValue,
302
+ pageSize
303
+ }: {
304
+ message: string
305
+ choices: Array<{ name: string; value: any; disabled?: boolean | string }>
306
+ defaultValue?: any
307
+ pageSize?: number
308
+ }): Promise<any> {
309
+ // Check if --yes flag is set
310
+ const cliOptions = await this.WorkspaceCli.cliOptions
311
+ const yes = cliOptions?.yes || false
312
+
313
+ // If --yes is set, return default value
314
+ if (yes) {
315
+ if (defaultValue === undefined) {
316
+ throw new Error(`Cannot use --yes flag without a default value for select: ${message}`)
317
+ }
318
+ return defaultValue
319
+ }
320
+
321
+ // Otherwise, load inquirer and prompt user
322
+ const inquirer = await import('inquirer')
323
+ try {
324
+ // Resolve defaultValue to the actual choice reference for inquirer v12
325
+ // inquirer v12 matches default by === on choice.value, so we need
326
+ // the exact reference from the choices array, not a separate object.
327
+ let resolvedDefault: any = undefined
328
+ if (defaultValue !== undefined) {
329
+ const match = choices.find(c =>
330
+ JSON.stringify(c.value) === JSON.stringify(defaultValue)
331
+ )
332
+ if (match) resolvedDefault = match.value
333
+ }
334
+ const { value } = await inquirer.default.prompt([
335
+ {
336
+ type: 'list',
337
+ name: 'value',
338
+ message,
339
+ choices,
340
+ default: resolvedDefault,
341
+ pageSize: pageSize || 10
342
+ }
343
+ ])
344
+ return value
345
+ } catch (error: any) {
346
+ if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
347
+ const chalk = (await import('chalk')).default
348
+ console.log(chalk.red('\n\nABORTED\n'))
349
+ process.exit(0)
350
+ }
351
+ throw error
352
+ }
353
+ }
354
+ },
355
+ setupPrompt: {
356
+ type: CapsulePropertyTypes.Function,
357
+ value: async function (this: any, {
358
+ title,
359
+ description,
360
+ message,
361
+ defaultValue,
362
+ validate,
363
+ configPath,
364
+ configKey,
365
+ onSuccess,
366
+ onAbort
367
+ }: {
368
+ title: string
369
+ description?: string | string[]
370
+ message: string
371
+ defaultValue: string
372
+ validate?: (input: string) => boolean | string
373
+ configPath: string[]
374
+ configKey?: string
375
+ onSuccess?: (value: string) => Promise<void> | void
376
+ onAbort?: () => Promise<void> | void
377
+ }): Promise<string> {
378
+ const chalk = (await import('chalk')).default
379
+
380
+ console.log(chalk.cyan(`\n${title}\n`))
381
+ if (description) {
382
+ const lines = Array.isArray(description) ? description : [description]
383
+ for (const line of lines) {
384
+ console.log(chalk.gray(` ${line}`))
385
+ }
386
+ console.log('')
387
+ }
388
+
389
+ try {
390
+ const value = await this.input({
391
+ message,
392
+ defaultValue,
393
+ validate
394
+ })
395
+
396
+ if (onSuccess) {
397
+ await onSuccess(value)
398
+ }
399
+
400
+ const displayKey = configKey || configPath.join('.')
401
+ console.log(chalk.green(`\n ✓ Updated config with ${displayKey}: ${value}\n`))
402
+
403
+ return value
404
+ } catch (error: any) {
405
+ if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
406
+ console.log(chalk.red('\n\nABORTED\n'))
407
+ if (onAbort) {
408
+ await onAbort()
409
+ }
410
+ process.exit(0)
411
+ }
412
+ throw error
413
+ }
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }, {
419
+ importMeta: import.meta,
420
+ importStack: makeImportStack(),
421
+ capsuleName: capsule['#'],
422
+ ambientReferences: {
423
+ globalPromptQueue,
424
+ globalActivePrompts,
425
+ globalShownTitles,
426
+ globalIsProcessing
427
+ }
428
+ })
429
+ }
430
+ capsule['#'] = '@stream44.studio/t44/caps/WorkspacePrompt'
@@ -0,0 +1,39 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ function w_log {
6
+ if [[ "$1" == "var" ]]; then
7
+ echo "\033[1;37m[$F_WORKSPACE_BASENAME] $2\033[0m"
8
+ elif [[ "$1" == "info" ]]; then
9
+ echo "\033[0;36m[$F_WORKSPACE_BASENAME] $2\033[0m"
10
+ elif [[ "$1" == "start" ]]; then
11
+ echo "\033[0;36m[$F_WORKSPACE_BASENAME] $2 ...\033[0m"
12
+ elif [[ "$1" == "notice" ]]; then
13
+ echo "\n\033[0;36m[$F_WORKSPACE_BASENAME] NOTICE: $2 ...\033[0m\n"
14
+ elif [[ "$1" == "success" ]]; then
15
+ echo "\033[0;32m[$F_WORKSPACE_BASENAME] $2\033[0m"
16
+ elif [[ "$1" == "error" ]]; then
17
+ echo "\033[0;31m[$F_WORKSPACE_BASENAME] $2\033[0m" >&2
18
+ fi
19
+ }
20
+
21
+ function initWorkspace {
22
+
23
+ #${COMMANDS}
24
+
25
+ }
26
+
27
+
28
+ if [ -z "$F_WORKSPACE_DIR" ]; then
29
+ w_log "error" "ERROR: 'F_WORKSPACE_DIR' env var not set!"
30
+ else
31
+
32
+ if [ -z "$F_WORKSPACE_BASENAME" ]; then
33
+ export F_WORKSPACE_BASENAME="$(basename "${F_WORKSPACE_DIR}")"
34
+ fi
35
+
36
+ initWorkspace ${@:1}
37
+ fi
38
+
39
+ set +e
@@ -0,0 +1,104 @@
1
+
2
+ import { join, resolve, relative } from 'path'
3
+ import { readFile } from 'fs/promises'
4
+ import { $ } from 'bun'
5
+
6
+ export async function capsule({
7
+ encapsulate,
8
+ CapsulePropertyTypes,
9
+ makeImportStack
10
+ }: {
11
+ encapsulate: any
12
+ CapsulePropertyTypes: any
13
+ makeImportStack: any
14
+ }) {
15
+ return encapsulate({
16
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
17
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
18
+ '#@stream44.studio/t44/structs/WorkspaceCliConfig': {
19
+ as: '$WorkspaceCliConfig'
20
+ },
21
+ '#@stream44.studio/t44/structs/WorkspaceShellConfig': {
22
+ as: '$ShellConfig'
23
+ },
24
+ '#': {
25
+ WorkspaceConfig: {
26
+ type: CapsulePropertyTypes.Mapping,
27
+ value: '@stream44.studio/t44/caps/WorkspaceConfig'
28
+ },
29
+ run: {
30
+ type: CapsulePropertyTypes.Function,
31
+ value: async function (this: any, { args }: any): Promise<void> {
32
+
33
+ const env = await this.$ShellConfig.env
34
+ const config = await this.WorkspaceConfig.config as any
35
+ const cliConfig = await this.$WorkspaceCliConfig.config
36
+
37
+ for (const name in env) {
38
+ process.stdout.write(`export ${name}="${env[name]}"\n`)
39
+ }
40
+
41
+ process.stdout.write('\n')
42
+
43
+ // Load the shell script template
44
+ const shellScriptModule = import.meta.resolve('./WorkspaceShell.sh')
45
+ const shellScriptPath = shellScriptModule.replace('file://', '')
46
+ let shellScript = await readFile(shellScriptPath, 'utf-8')
47
+
48
+ // Generate commands dynamically from shell.commands
49
+ const shellConfig = await this.$ShellConfig.config
50
+ const shellCommands = shellConfig?.shell?.commands || {}
51
+ const cliCommands = cliConfig?.cli?.commands || {}
52
+ const commandNames = Object.keys(shellCommands).sort()
53
+
54
+ // Generate shell functions
55
+ const helpLines: string[] = []
56
+ for (const cmdName of commandNames) {
57
+ const cmdConfig = shellCommands[cmdName]
58
+
59
+ let cmdBody = ''
60
+
61
+ // Check if this is a cliCommand reference
62
+ if (cmdConfig.cliCommand) {
63
+ const cliCommandName = cmdConfig.cliCommand
64
+ // Auto-generate the shell code to call the CLI command
65
+ cmdBody = `(
66
+ cd "$F_WORKSPACE_DIR" &&
67
+ "\${F_WORKSPACE_IMPL_DIR}/bin/workspace" ${cliCommandName} \$@
68
+ )`
69
+ } else {
70
+ cmdBody = cmdConfig.cmd || ''
71
+ }
72
+
73
+ helpLines.push(` function ${cmdName} {`)
74
+ for (const line of cmdBody.split('\n')) {
75
+ helpLines.push(` ${line}`)
76
+ }
77
+ helpLines.push(` }`)
78
+ helpLines.push(``)
79
+ }
80
+
81
+ // Generate alias for h if it exists
82
+ if (shellCommands.h) {
83
+ helpLines.push(` alias h='cd "\${F_WORKSPACE_DIR}"'`)
84
+ helpLines.push(``)
85
+ }
86
+
87
+ const commandsBlock = helpLines.join('\n')
88
+
89
+ // Replace the placeholder in the shell script
90
+ shellScript = shellScript.replace('#${COMMANDS}', commandsBlock)
91
+
92
+ // Remove the shebang line and output
93
+ process.stdout.write(shellScript.split('\n').slice(1).join('\n'))
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }, {
99
+ importMeta: import.meta,
100
+ importStack: makeImportStack(),
101
+ capsuleName: capsule['#'],
102
+ })
103
+ }
104
+ capsule['#'] = '@stream44.studio/t44/caps/WorkspaceShell'
@@ -0,0 +1,64 @@
1
+ $schema: ../../../../../../../.~o/workspace.foundation/@t44.sh~t44~caps~JsonSchemas/@t44.sh~t44~structs~WorkspaceConfigFile.json
2
+ "#@stream44.studio/t44/structs/WorkspaceShellConfig":
3
+ env:
4
+ default: null
5
+ force:
6
+ F_WORKSPACE_DIR: resolve('${__dirname}/..')
7
+ shell:
8
+ commands:
9
+ "44":
10
+ description: Run workspace CLI commands.
11
+ cmd: |
12
+ "${F_WORKSPACE_IMPL_DIR}/bin/t44" $@
13
+ help:
14
+ description: Display help for shell commands.
15
+ cmd: |
16
+ "${F_WORKSPACE_IMPL_DIR}/bin/shell" --help
17
+ a:
18
+ description: Re-activate workspace environment.
19
+ cmd: |
20
+ w_log "start" "Re-activating workspace"
21
+
22
+ source "$F_WORKSPACE_ACTIVATE_BIN_PATH"
23
+
24
+ w_log "success" "Workspace Activated OK!"
25
+ i:
26
+ description: Re-install workspace dependencies (removes node_modules and runs bun install).
27
+ cmd: |
28
+ w_log "start" "Re-installing workspace"
29
+
30
+ (
31
+ cd "${F_WORKSPACE_DIR}" &&
32
+ find . -name 'node_modules' -type d -prune -exec rm -rf {} + 2>/dev/null || true &&
33
+ bun install
34
+ )
35
+
36
+ w_log "success" "Workspace Re-installed OK!"
37
+ info:
38
+ description: Display workspace information.
39
+ cmd: |
40
+ (
41
+ cd "${F_WORKSPACE_DIR}" &&
42
+ "${F_WORKSPACE_IMPL_DIR}/bin/t44" info ${@:2}
43
+ )
44
+ p:
45
+ description: Push project repositories.
46
+ cmd: |
47
+ w_log "start" "Push project repositories"
48
+
49
+ (
50
+ cd "${F_WORKSPACE_DIR}" &&
51
+ "${F_WORKSPACE_IMPL_DIR}/bin/t44" push $@
52
+ )
53
+
54
+ w_log "success" "Project repositories pushed OK!"
55
+ h:
56
+ description: Navigate to workspace home directory.
57
+ cmd: |
58
+ cd "${F_WORKSPACE_DIR}"
59
+ dev:
60
+ cliCommand: dev
61
+ deploy:
62
+ cliCommand: deploy
63
+ createdAt: "2026-02-11T01:01:49.799Z"
64
+ updatedAt: "2026-02-11T01:01:49.799Z"